Dropdown Menu

A menu that drops down when triggered, showing a list of options

Basic Menus

Simple dropdown menus with icons and shortcuts


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4	import { User, Settings, LogOut, Plus } from "@kareyes/aether/icons";
5
6	const basicMenuItems = [
7		{ label: "Profile", icon: User, onSelect: () => console.log("Profile clicked") },
8		{ label: "Settings", icon: Settings, onSelect: () => console.log("Settings clicked") },
9		{ type: "separator" },
10		{ label: "Logout", icon: LogOut, variant: "destructive", onSelect: () => console.log("Logout clicked") },
11	];
12</script>
13
14	<div class="flex flex-wrap gap-4">
15		<DropdownMenu
16			triggerText="User Menu"
17			items={basicMenuItems}
18		/>
19
20		<DropdownMenu
21			triggerText="File Actions"
22			triggerVariant="default"
23			items={fileMenuItems}
24		/>
25
26		<DropdownMenu
27			triggerText="With Icon"
28			triggerIcon={Plus}
29			triggerVariant="secondary"
30			items={basicMenuItems}
31		/>
32
33		<DropdownMenu
34			triggerText="No Chevron"
35			showChevron={false}
36			items={basicMenuItems}
37		/>
38	</div>

Interactive Menus

Menus with checkboxes and radio groups

Status Bar: ✓ Toolbar: ✓ Sidebar: ✗
Current theme: light
Selected plan: free

Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4
5	let statusBarChecked = $state(true);
6	let toolbarChecked = $state(true);
7	let sidebarChecked = $state(false);
8	let theme = $state("light");
9
10	const viewMenuItems = $derived([
11		{ type: "label", label: "View Options" },
12		{ type: "checkbox", label: "Show Status Bar", checked: statusBarChecked, onSelect: () => statusBarChecked = !statusBarChecked },
13		{ type: "checkbox", label: "Show Toolbar", checked: toolbarChecked, onSelect: () => toolbarChecked = !toolbarChecked },
14		{ type: "checkbox", label: "Show Sidebar", checked: sidebarChecked, onSelect: () => sidebarChecked = !sidebarChecked },
15	]);
16
17	const themeMenuItems = $derived([
18		{
19			type: "radio",
20			label: "Select Theme",
21			value: theme,
22			items: [
23				{ label: "Light", value: "light" },
24				{ label: "Dark", value: "dark" },
25				{ label: "System", value: "system" },
26			],
27			onValueChange: (value) => { theme = value; },
28		},
29	]);
30</script>
31
32	<div class="space-y-4">
33		<div class="flex flex-wrap gap-4">
34			<DropdownMenu
35				triggerText="View Options"
36				items={viewMenuItems}
37			/>
38			<div class="flex items-center gap-4 text-sm text-muted-foreground">
39				<span>Status Bar: {statusBarChecked ? '✓' : '✗'}</span>
40				<span>Toolbar: {toolbarChecked ? '✓' : '✗'}</span>
41				<span>Sidebar: {sidebarChecked ? '✓' : '✗'}</span>
42			</div>
43		</div>
44
45		<div class="flex flex-wrap gap-4">
46			<DropdownMenu
47				triggerText={`Theme: ${theme}`}
48				items={themeMenuItems}
49			/>
50			<div class="text-sm text-muted-foreground">
51				Current theme: <span class="font-medium">{theme}</span>
52			</div>
53		</div>
54
55		<div class="flex flex-wrap gap-4">
56			<DropdownMenu
57				triggerText={`Plan: ${selectedPlan}`}
58				triggerVariant="outline"
59				items={planMenuItems}
60			/>
61			<div class="text-sm text-muted-foreground">
62				Selected plan: <span class="font-medium capitalize">{selectedPlan}</span>
63			</div>
64		</div>
65	</div>

Grouped Menu

Organize menu items into labeled groups


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4	import { User, Settings, Bell, Edit, Copy, Trash2, HelpCircle, LogOut } from "@kareyes/aether/icons";
5
6	const groupedMenuItems = [
7		{
8			label: "Account",
9			items: [
10				{ label: "Profile", icon: User, onSelect: () => console.log("Profile") },
11				{ label: "Settings", icon: Settings, onSelect: () => console.log("Settings") },
12				{ label: "Notifications", icon: Bell, onSelect: () => console.log("Notifications") },
13			],
14		},
15		{ type: "separator" },
16		{
17			label: "Actions",
18			items: [
19				{ label: "Edit", icon: Edit, onSelect: () => console.log("Edit") },
20				{ label: "Copy", icon: Copy, onSelect: () => console.log("Copy") },
21				{ label: "Delete", icon: Trash2, variant: "destructive", onSelect: () => console.log("Delete") },
22			],
23		},
24		{ type: "separator" },
25		{
26			items: [
27				{ label: "Help", icon: HelpCircle, onSelect: () => console.log("Help") },
28				{ label: "Logout", icon: LogOut, variant: "destructive", onSelect: () => console.log("Logout") },
29			],
30		},
31	];
32</script>
33
34<DropdownMenu triggerText="Actions" triggerVariant="outline" items={groupedMenuItems} />

Complex Combined Menu

Combining groups, radio, checkboxes, and shortcuts

Theme
light
View Options
Status Bar: ✓
Toolbar: ✓

Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4	import { FileText, Edit2, Share2, Trash2, Copy, Download, Mail, MessageSquare, Settings } from "@kareyes/aether/icons";
5
6
7	const complexMenuItems = $derived([
8		{
9			label: "My Account",
10			items: [
11				{ label: "Profile", icon: User, shortcut: "⌘P", onSelect: () => console.log("Profile") },
12				{ label: "Settings", icon: Settings, shortcut: "⌘,", onSelect: () => console.log("Settings") },
13			],
14		},
15		{ type: "separator" as const },
16		{
17			type: "radio" as const,
18			label: "Theme",
19			value: theme,
20			items: [
21				{ label: "Light", value: "light" },
22				{ label: "Dark", value: "dark" },
23				{ label: "System", value: "system" },
24			],
25			onValueChange: (value: string) => { theme = value; },
26		},
27		{ type: "separator" as const },
28		{
29			label: "View",
30			items: [
31				{ type: "checkbox" as const, label: "Status Bar", checked: statusBarChecked, onSelect: () => statusBarChecked = !statusBarChecked },
32				{ type: "checkbox" as const, label: "Toolbar", checked: toolbarChecked, onSelect: () => toolbarChecked = !toolbarChecked },
33			],
34		},
35		{ type: "separator" as const },
36		{
37			items: [
38				{ label: "Help", icon: HelpCircle, shortcut: "⌘?", onSelect: () => console.log("Help") },
39				{ label: "Logout", icon: LogOut, variant: "destructive" as const, shortcut: "⌘Q", onSelect: () => console.log("Logout") },
40			],
41		},
42	]);
43</script>
44
45	<div class="space-y-4">
46		<DropdownMenu
47			triggerText="Account Settings"
48			triggerVariant="default"
49			items={complexMenuItems}
50		/>
51		<div class="grid grid-cols-2 gap-4 max-w-md">
52			<div class="p-3 rounded-md bg-muted/50">
53				<div class="text-xs font-medium text-muted-foreground mb-1">Theme</div>
54				<div class="text-sm font-medium">{theme}</div>
55			</div>
56			<div class="p-3 rounded-md bg-muted/50">
57				<div class="text-xs font-medium text-muted-foreground mb-1">View Options</div>
58				<div class="text-xs space-y-0.5">
59					<div>Status Bar: {statusBarChecked ? '✓' : '✗'}</div>
60					<div>Toolbar: {toolbarChecked ? '✓' : '✗'}</div>
61				</div>
62			</div>
63		</div>
64	</div>

Trigger Variants

Different button styles for the menu trigger


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4	import { User, Settings, LogOut } from "@kareyes/aether/icons";
5
6	const items = [
7		{ label: "Profile", icon: User, onSelect: () => {} },
8		{ label: "Settings", icon: Settings, onSelect: () => {} },
9		{ type: "separator" },
10		{ label: "Logout", icon: LogOut, variant: "destructive", onSelect: () => {} },
11	];
12</script>
13
14<div class="flex flex-wrap gap-4">
15	<DropdownMenu triggerText="Default" triggerVariant="default" items={items} />
16	<DropdownMenu triggerText="Secondary" triggerVariant="secondary" items={items} />
17	<DropdownMenu triggerText="Outline" triggerVariant="outline" items={items} />
18	<DropdownMenu triggerText="Ghost" triggerVariant="ghost" items={items} />
19	<DropdownMenu triggerText="Destructive" triggerVariant="destructive" items={items} />
20</div>

Sizes

Available trigger button sizes


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4
5	const items = [
6		{ label: "Profile", onSelect: () => {} },
7		{ label: "Settings", onSelect: () => {} },
8	];
9</script>
10
11<div class="flex flex-wrap items-center gap-4">
12	<DropdownMenu triggerText="Small" triggerSize="sm" items={items} />
13	<DropdownMenu triggerText="Default" triggerSize="default" items={items} />
14	<DropdownMenu triggerText="Large" triggerSize="lg" items={items} />
15</div>

Content Alignment

Control menu content alignment


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4
5	const items = [
6		{ label: "Profile", onSelect: () => {} },
7		{ label: "Settings", onSelect: () => {} },
8	];
9</script>
10
11<div class="flex flex-wrap gap-4">
12	<DropdownMenu triggerText="Align Start" align="start" items={items} />
13	<DropdownMenu triggerText="Align Center" align="center" items={items} />
14	<DropdownMenu triggerText="Align End" align="end" items={items} />
15</div>

With Submenu

Nested menus for hierarchical navigation


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu } from "@kareyes/aether";
4	import { User, Share2, Copy, Download, Mail, MessageSquare, Settings } from "@kareyes/aether/icons";
5
6	const shareMenuItems = [
7		{ label: "Profile", icon: User, onSelect: () => console.log("Profile") },
8		{
9			type: "submenu" as const,
10			label: "Share",
11			icon: Share2,
12			items: [
13				{ label: "Copy Link", icon: Copy, onSelect: () => console.log("Copy Link") },
14				{ label: "Download", icon: Download, onSelect: () => console.log("Download") },
15				{ type: "separator" as const },
16				{ label: "Email", icon: Mail, onSelect: () => console.log("Email") },
17				{ label: "Message", icon: MessageSquare, onSelect: () => console.log("Message") },
18			]
19		},
20		{ type: "separator" as const },
21		{ label: "Settings", icon: Settings, onSelect: () => console.log("Settings") },
22	];
23
24	const fileMenuWithSubmenu = [
25		{ label: "New File", icon: Plus, shortcut: "⌘N", onSelect: () => console.log("New file") },
26		{
27			type: "submenu" as const,
28			label: "New From Template",
29			icon: FileText,
30			items: [
31				{ label: "Text Document", icon: FileText, onSelect: () => console.log("Text") },
32				{ label: "Image", icon: Image, onSelect: () => console.log("Image") },
33				{ label: "Video", icon: Video, onSelect: () => console.log("Video") },
34				{ label: "Audio", icon: Music, onSelect: () => console.log("Audio") },
35			]
36		},
37		{ type: "separator" as const },
38		{ label: "Upload", icon: Upload, shortcut: "⌘U", onSelect: () => console.log("Upload") },
39		{ label: "Download", icon: Download, shortcut: "⌘D", onSelect: () => console.log("Download") },
40		{ type: "separator" as const },
41		{ label: "Delete", icon: Trash2, variant: "destructive" as const, shortcut: "⌘⌫", onSelect: () => console.log("Delete") },
42	];
43
44	const nestedSubmenuItems = [
45		{ label: "Home", onSelect: () => console.log("Home") },
46		{
47			type: "submenu" as const,
48			label: "File",
49			icon: Folder,
50			items: [
51				{ label: "New", icon: Plus, shortcut: "⌘N", onSelect: () => console.log("New") },
52				{
53					type: "submenu" as const,
54					label: "Open Recent",
55					items: [
56						{ label: "Document 1.txt", icon: File, onSelect: () => console.log("Doc 1") },
57						{ label: "Document 2.txt", icon: File, onSelect: () => console.log("Doc 2") },
58						{ label: "Document 3.txt", icon: File, onSelect: () => console.log("Doc 3") },
59					]
60				},
61				{ type: "separator" as const },
62				{ label: "Save", shortcut: "⌘S", onSelect: () => console.log("Save") },
63			]
64		},
65		{ type: "separator" as const },
66		{ label: "Exit", variant: "destructive" as const, onSelect: () => console.log("Exit") },
67	];
68</script>
69<div class="flex flex-wrap gap-4">
70	<DropdownMenu
71		triggerText="Share Menu"
72		triggerIcon={Share2}
73		items={shareMenuItems}
74	/>
75
76	<DropdownMenu
77		triggerText="File Menu"
78		triggerVariant="outline"
79		items={fileMenuWithSubmenu}
80	/>
81
82	<DropdownMenu
83		triggerText="Nested Submenus"
84		triggerVariant="secondary"
85		items={nestedSubmenuItems}
86	/>
87</div>

Custom Trigger

Use the trigger snippet prop to provide custom trigger elements like labels, avatars, or any custom component.


Code Svelte
1
2<script lang="ts">
3	import { DropdownMenu, DropdownMenuPrimitives } from "@kareyes/aether";
4	import { User, LogOut,  Plus, Edit, Copy, Share2, Trash2, Bell, Shield, HelpCircle } from "@kareyes/aether/icons";
5
6	const customTriggerMenuItems = [
7		{ label: "New Item", icon: Plus, shortcut: "⌘N", onSelect: () => console.log("New Item") },
8		{ label: "Edit", icon: Edit, shortcut: "⌘E", onSelect: () => console.log("Edit") },
9		{ label: "Duplicate", icon: Copy, shortcut: "⌘D", onSelect: () => console.log("Duplicate") },
10		{ type: "separator" as const },
11		{ label: "Share", icon: Share2, onSelect: () => console.log("Share") },
12		{ type: "separator" as const },
13		{ label: "Delete", icon: Trash2, variant: "destructive" as const, shortcut: "⌘⌫", onSelect: () => console.log("Delete") },
14	];
15
16	const labelTriggerMenuItems = [
17		{ label: "My Profile", icon: User, onSelect: () => console.log("Profile") },
18		{ label: "Account Settings", icon: Settings, onSelect: () => console.log("Settings") },
19		{ label: "Notifications", icon: Bell, onSelect: () => console.log("Notifications") },
20		{ type: "separator" as const },
21		{ label: "Privacy & Security", icon: Shield, onSelect: () => console.log("Privacy") },
22		{ label: "Help Center", icon: HelpCircle, onSelect: () => console.log("Help") },
23		{ type: "separator" as const },
24		{ label: "Sign Out", icon: LogOut, variant: "destructive" as const, onSelect: () => console.log("Sign Out") },
25	];
26</script>
27
28<div class="flex flex-wrap gap-6">
29	<!-- Label as Trigger -->
30	<DropdownMenu items={labelTriggerMenuItems}>
31		{#snippet trigger()}
32			<DropdownMenuPrimitive.Trigger>
33				{#snippet child({ props })}
34					<div {...props} class="cursor-pointer hover:opacity-80 transition-opacity">
35						<div class="flex items-center gap-2">
36							<div class="w-8 h-8 rounded-full bg-gradient-to-br from-purple-500 to-pink-500 flex items-center justify-center text-white text-sm font-semibold">
37								JD
38							</div>
39							<span class="text-sm font-medium">John Doe</span>
40							<ChevronDown class="size-3 text-muted-foreground" />
41						</div>
42					</div>
43				{/snippet}
44			</DropdownMenuPrimitive.Trigger>
45		{/snippet}
46	</DropdownMenu>
47
48	<!-- Text Label as Trigger -->
49	<DropdownMenu items={customTriggerMenuItems}>
50		{#snippet trigger()}
51			<DropdownMenuPrimitive.Trigger>
52				{#snippet child({ props })}
53					<span
54						{...props}
55						class="text-sm font-medium text-primary hover:underline cursor-pointer"
56					>
57						Actions Menu ▼
58					</span>
59				{/snippet}
60			</DropdownMenuPrimitive.Trigger>
61		{/snippet}
62	</DropdownMenu>
63</div>