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.

<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>

Installation

Copy the command below and run it in your terminal.

bunx @sivir-ui/svelte add dropdown-menu

Usage

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 &amp; 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.