Dropdown Menu
Opens below the trigger, aligned to its start edge, and closes when you pick an item. Escape closes the deepest open submenu first.
Installation
Copy the command below and run it in your terminal.
bunx @sivir-ui/svelte add dropdown-menupnpm dlx @sivir-ui/svelte add dropdown-menunpx @sivir-ui/svelte add dropdown-menuyarn dlx @sivir-ui/svelte add dropdown-menuUsage
Run an action with callback or onclick on DropdownMenu.Item. Bind checked on CheckboxItem and value on RadioGroup to read the selection.
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
import Shortcut from '@sivir-ui/svelte/components/shortcut';
let showArchived = $state(false);
<DropdownMenu.Root>
<DropdownMenu.Trigger>View</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item callback={() => navigator.clipboard.writeText(location.href)}>
Copy link
<Shortcut shortcut="shift+cmd+C" />
</DropdownMenu.Item>
<DropdownMenu.CheckboxItem bind:checked={showArchived}>
Show archived
</DropdownMenu.CheckboxItem>
</DropdownMenu.Content>
</DropdownMenu.Root>Examples
Basic menu
<script lang="ts">
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger size="md">Edit</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item>Rename</DropdownMenu.Item>
<DropdownMenu.Item>Duplicate</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Grouped items
<script lang="ts">
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import HelpCircle from '@lucide/svelte/icons/circle-help';
import CreditCard from '@lucide/svelte/icons/credit-card';
import Folder from '@lucide/svelte/icons/folder-git-2';
import LogOut from '@lucide/svelte/icons/log-out';
import Settings from '@lucide/svelte/icons/settings';
import User from '@lucide/svelte/icons/user';
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger variant="outline" size="md">
<User size={13} />
Aidan
<ChevronDown size={11} class="text-foreground-muted" />
</DropdownMenu.Trigger>
<DropdownMenu.Content class="min-w-[14rem]">
<DropdownMenu.Label>Account</DropdownMenu.Label>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><User size={13} /> Profile</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><Folder size={13} /> Your projects</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><CreditCard size={13} /> Billing</span>
</DropdownMenu.Item>
<DropdownMenu.Label>Settings</DropdownMenu.Label>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><Settings size={13} /> Preferences</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><HelpCircle size={13} /> Help & support</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><LogOut size={13} /> Log out</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Row actions
<script lang="ts">
import Archive from '@lucide/svelte/icons/archive';
import MoreHorizontal from '@lucide/svelte/icons/ellipsis';
import Copy2 from '@lucide/svelte/icons/files';
import Pencil from '@lucide/svelte/icons/pencil';
import Trash from '@lucide/svelte/icons/trash-2';
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
import Shortcut from '@sivir-ui/svelte/components/shortcut';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger variant="ghost" size="icon" aria-label="Row actions">
<MoreHorizontal size={14} />
</DropdownMenu.Trigger>
<DropdownMenu.Content class="min-w-[12rem]">
<DropdownMenu.Item>
<span class="flex items-center gap-2"><Pencil size={13} /> Rename</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><Copy2 size={13} /> Duplicate</span>
<Shortcut shortcut="cmd+D" />
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><Archive size={13} /> Archive</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2 text-[var(--color-error)]">
<Trash size={13} />
Delete
</span>
<Shortcut shortcut="backspace" />
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Share menu
<script lang="ts">
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import EyeOff from '@lucide/svelte/icons/eye-off';
import Copy2 from '@lucide/svelte/icons/files';
import Send from '@lucide/svelte/icons/send';
import User from '@lucide/svelte/icons/user';
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
import Shortcut from '@sivir-ui/svelte/components/shortcut';
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger variant="outline" size="md">
<Send size={13} />
Share
<ChevronDown size={11} class="text-foreground-muted" />
</DropdownMenu.Trigger>
<DropdownMenu.Content class="min-w-[14rem]">
<DropdownMenu.Label>Workspace</DropdownMenu.Label>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><User size={13} /> Invite collaborator</span>
</DropdownMenu.Item>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><Copy2 size={13} /> Copy link</span>
<Shortcut shortcut="shift+cmd+C" />
</DropdownMenu.Item>
<DropdownMenu.Label>Public</DropdownMenu.Label>
<DropdownMenu.Item>
<span class="flex items-center gap-2"><EyeOff size={13} /> Make private</span>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Sort menu
<script lang="ts">
import Check from '@lucide/svelte/icons/check';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import Star from '@lucide/svelte/icons/star';
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
const options = ['Most starred', 'Recently updated', 'Alphabetical', 'Oldest first'];
let selected = $state(options[0]);
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger variant="ghost" size="md" class="w-48 justify-between">
<Star size={13} />
{selected}
<ChevronDown size={11} class="text-foreground-muted" />
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-48">
{#each options as option (option)}
<DropdownMenu.Item callback={() => (selected = option)}>
<span>{option}</span>
{#if selected === option}
<Check size={12} class="ml-auto text-primary" />
{/if}
</DropdownMenu.Item>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>
Configuration submenu
<script lang="ts">
import Check from '@lucide/svelte/icons/check';
import ChevronDown from '@lucide/svelte/icons/chevron-down';
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
let model = $state('GPT-5.6 Sol');
let effort = $state('High');
let speed = $state('Standard');
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger variant="outline" size="md" class="w-44 justify-between">
<span>Configuration</span>
<ChevronDown size={15} class="text-foreground-muted" />
</DropdownMenu.Trigger>
<DropdownMenu.Content class="w-44">
<DropdownMenu.Label>Configuration</DropdownMenu.Label>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Model</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent class="min-w-[12rem]">
<DropdownMenu.Item callback={() => (model = 'GPT-5.6 Sol')}>
<span>GPT-5.6 Sol</span>
{#if model === 'GPT-5.6 Sol'}
<Check size={16} class="ml-auto" />
{/if}
</DropdownMenu.Item>
<DropdownMenu.Item callback={() => (model = 'Opus 4.6')}>
<span>Opus 4.6</span>
{#if model === 'Opus 4.6'}
<Check size={16} class="ml-auto" />
{/if}
</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Effort</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent class="min-w-[10rem]">
{#each ['Low', 'Medium', 'High'] as option (option)}
<DropdownMenu.Item callback={() => (effort = option)}>
<span>{option}</span>
{#if effort === option}
<Check size={16} class="ml-auto" />
{/if}
</DropdownMenu.Item>
{/each}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Speed</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent class="min-w-[10rem]">
{#each ['Fast', 'Standard', 'Deliberate'] as option (option)}
<DropdownMenu.Item callback={() => (speed = option)}>
<span>{option}</span>
{#if speed === option}
<Check size={16} class="ml-auto" />
{/if}
</DropdownMenu.Item>
{/each}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Content>
</DropdownMenu.Root>
Each SubTrigger opens its SubContent on hover.
Dynamic width
<script lang="ts">
import Check from '@lucide/svelte/icons/check';
import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu';
const densities = ['Compact', 'Comfortable', 'Spacious'];
let density = $state(densities[1]);
</script>
<DropdownMenu.Root>
<DropdownMenu.Trigger size="md">View</DropdownMenu.Trigger>
<DropdownMenu.Content dynamic>
<DropdownMenu.Label>Appearance</DropdownMenu.Label>
<DropdownMenu.Item>Edit dashboard settings</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Density</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent dynamic>
{#each densities as option (option)}
<DropdownMenu.Item callback={() => (density = option)}>
<span class="flex-1">{option}</span>
{#if density === option}
<Check size={13} aria-hidden="true" />
{/if}
</DropdownMenu.Item>
{/each}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Item>Reset everything to defaults</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Set dynamic on Content or SubContent to size the panel to its widest item plus 16px.
Anatomy
DropdownMenu.Root - Provides dropdown menu state.
DropdownMenu.Trigger - Opens the dropdown menu.
DropdownMenu.Content - Renders the menu surface.
DropdownMenu.Label - Labels a menu section.
DropdownMenu.Item - Renders a menu action.
DropdownMenu.CheckboxItem - Renders a checkable menu item.
DropdownMenu.RadioGroup - Manages a single-selection group.
DropdownMenu.RadioItem - Renders a radio option.
DropdownMenu.Separator - Separates menu items.
DropdownMenu.Sub - Provides submenu state.
DropdownMenu.SubTrigger - Opens a submenu.
DropdownMenu.SubContent - Renders a submenu surface.
<script lang="ts"> import ChevronDown from '@lucide/svelte/icons/chevron-down'; import CreditCard from '@lucide/svelte/icons/credit-card'; import LifeBuoy from '@lucide/svelte/icons/life-buoy'; import LogOut from '@lucide/svelte/icons/log-out'; import Settings from '@lucide/svelte/icons/settings'; import User from '@lucide/svelte/icons/user'; import * as DropdownMenu from '@sivir-ui/svelte/components/dropdown-menu'; import Shortcut from '@sivir-ui/svelte/components/shortcut'; </script> <DropdownMenu.Root> <DropdownMenu.Trigger variant="outline" size="md"> My account <ChevronDown size={16} class="text-foreground-muted" /> </DropdownMenu.Trigger> <DropdownMenu.Content class="min-w-[16rem]"> <DropdownMenu.Label> <span class="text-[0.7rem] text-foreground-muted">aidan@sivir.dev</span> </DropdownMenu.Label> <DropdownMenu.Item> <span class="flex items-center gap-2"><User size={13} /> Profile</span> <Shortcut shortcut="shift+cmd+P" /> </DropdownMenu.Item> <DropdownMenu.Item> <span class="flex items-center gap-2"><CreditCard size={13} /> Billing</span> <Shortcut shortcut="cmd+B" /> </DropdownMenu.Item> <DropdownMenu.Item> <span class="flex items-center gap-2"><Settings size={13} /> Settings</span> <Shortcut shortcut="cmd+," /> </DropdownMenu.Item> <DropdownMenu.Item> <span class="flex items-center gap-2"><LifeBuoy size={13} /> Help & feedback</span> </DropdownMenu.Item> <DropdownMenu.Item> <span class="flex items-center gap-2 text-[var(--color-error)]"> <LogOut size={13} /> Sign out </span> <Shortcut shortcut="shift+cmd+Q" /> </DropdownMenu.Item> </DropdownMenu.Content> </DropdownMenu.Root>