Overlays

Dropdown Menu

A menu of actions anchored to a button.

Installation

pnpm dlx @mielui/svelte add dropdown-menu

Usage

Item runs callback when chosen, then closes the menu. Arrow keys, Home, End, and typing a label move between enabled items, and submenus open with the arrow key that points at them. An onclick handler on an item can call event.preventDefault() to keep the menu open.

The panel is one floating layer with a single hairline edge; the theme's chrome.borders setting does not change it.

<script lang="ts">
    import * as DropdownMenu from '@mielui/svelte/components/dropdown-menu';
    import Kbd from '@mielui/svelte/components/kbd';

    function newFile() {
        console.log('New file');
    }
</script>

<DropdownMenu.Root>
    <DropdownMenu.Trigger>File</DropdownMenu.Trigger>
    <DropdownMenu.Content>
        <DropdownMenu.Item callback={newFile}>
            New file
            <Kbd shortcut="cmd+N" />
        </DropdownMenu.Item>
    </DropdownMenu.Content>
</DropdownMenu.Root>

Examples

Item callbacks

Each item runs a callback, and the line under the menu reports the last choice.

Basic menu

Grouped items

Row actions

Share menu

Sort menu

Configuration submenu

Nested choices for configuring a model, effort level, and response speed.

Dynamic width

Menus and submenus size to their longest item plus a buffer instead of hugging the trigger.

Glass surface

Set surface="glass" on DropdownMenu.Content for a translucent background with blur. Without a surface prop, the component follows --mielui-surface. An unset variable keeps it solid; surface="solid" overrides a global glass theme. The glass surface keeps an opaque fallback when backdrop filtering is unavailable and respects reduced-transparency preferences.

Checkbox and radio items

CheckboxItem toggles an option and closes the menu, like any other item. Bind checked, or listen with onCheckedChange, which receives the new state.

RadioGroup holds one choice among its RadioItem children. Bind value on the group, or use onValueChange to react to the new value. Each RadioItem needs its own value. A checked CheckboxItem shows a check and the selected RadioItem shows a dot.

Controlling the menu

Bind open on Root to open or close the menu from your own code. onOpenChange runs when the person opens or closes it, and stays quiet for changes you make through open yourself. Trigger and SubTrigger also take onopen, which runs just before that trigger opens its panel. Use it to load items when someone looks.

Panel behavior

Content shares its panel with Popover, with menu defaults. A press outside closes the menu unless you set allowClickOutside={false}. dismissLayer={false} removes the invisible layer that catches the first outside press, so the page stays clickable while the menu is open.

focusTrap and lockScroll are both off for menus, so Tab leaves the menu and the page still scrolls. Turn either on for a menu that should hold attention. portal={false} renders the panel in place instead of at the end of the document. refElement anchors the panel to another element, and tabindex sets the panel's tab index.

Items and other parts

Item runs callback when chosen and then closes the menu. It is built on Button, so disabled skips it, and loading with labels shows progress on a row that starts slow work. variant="destructive" colors the row for an action that removes something and tints the highlight while it is active. Item, CheckboxItem, RadioItem, Trigger and SubTrigger accept element to bind the DOM node, and onkeydown on Item runs before the menu handles the key. Rows always use the menu row style, so Button's other variants, size and unstyled do not apply to them. A row is always a button and does not take href. Navigate from callback.

Root, Content, Label, Separator, Sub, SubContent and RadioGroup render their children and accept class and style like any element.

API reference

DropdownMenu.Root

Provides dropdown menu state.

  • open Bindable
    boolean

    Whether it is open. Bind it to control the state from outside.

    Default false

  • onOpenChange
    ((open: boolean) => void)

    Called with the new state when the person opens or closes it, not when you set open.

  • children
    Snippet<[]>

    Content rendered inside.

DropdownMenu.CheckboxItem

  • checked Bindable
    boolean

    Whether the item is checked. Bindable.

    Default false

  • onCheckedChange
    ((checked: boolean) => void)

    Called with the new state when it changes.

  • children
    Snippet<[]>

    Content rendered inside.

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the DOM element.

  • class
    (string & (ClassValue | null))

    Classes added to the element.

DropdownMenu.Content

Renders the menu surface.

  • children Required
    Snippet<[]>

    Content rendered inside.

  • class
    string

    Classes added to the element.

  • dynamic
    boolean

    Lets the panel grow to fit its content instead of matching the trigger width.

    Default false

  • tabindex
    number

    Tab index of the panel.

    Default -1

  • role
    "dialog" | "alertdialog" | "menu" | "listbox" | "none"

    ARIA role of the panel.

    Default 'menu'

  • surface
    "solid" | "glass"

    Surface treatment. Omit it to inherit --mielui-surface from the theme.

  • allowClickOutside
    boolean

    Closes when the pointer is pressed outside it. Set false to keep it open until it is dismissed another way.

    Default true

  • dismissLayer
    boolean

    Render the full-viewport dismiss layer under the panel while open. Defaults to true. Set to false for triggers that must stay clickable while open (e.g. an input-style combobox trigger); outside pointer dismissal still applies via allowClickOutside.

    Default true

  • portal
    boolean

    Moves the panel to the end of the document so ancestors cannot clip it. Set false to render it in place.

    Default true

  • refElement
    VirtualElement

    Positions the panel against this element or virtual element instead of the trigger. The panel then opens to the right of it, as a submenu does.

  • focusTrap
    boolean

    Trap Tab focus inside the panel while open. Defaults to true.

    Default false

  • lockScroll
    boolean

    Lock document scrolling while the panel is open. Defaults to true.

    Default false

DropdownMenu.Item

Renders a menu action.

  • callback
    (() => void)

    Called when the item is chosen.

  • variant
    "destructive"

    Marks an action that deletes or cannot be undone. Omit it for a regular item.

  • children
    Snippet<[]>

    Content rendered inside.

  • onkeydown
    ((event: KeyboardEvent) => void)

    Called on key down, before the button handles the key itself.

  • onclick
    ((event: MouseEvent) => void)

    Called when the button is activated. Loading and disabled buttons do not call it.

  • status
    ButtonStatus

    Controlled visual state. Loading remains focusable and refuses activation.

  • disabled
    boolean

    Prevents activation. A disabled link loses its destination and is skipped by Tab.

    Default false

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the rendered DOM element. Type is the union of the two possible element types -- narrow at the use site:

  • loading
    boolean

    Convenience alias for status="loading".

  • labels
    ButtonLabels

    Overrides the built-in text and accessible names. Every key is optional and English is the fallback.

DropdownMenu.Label

Labels a menu section.

  • children Required
    Snippet<[]>

    Content rendered inside.

  • class
    string

    Classes added to the element.

DropdownMenu.RadioGroup

  • value Bindable
    string

    Value of the selected item. Bindable.

    Default ''

  • onValueChange
    ((value: string) => void)

    Called with the new value when it changes.

  • children
    Snippet<[]>

    Content rendered inside.

DropdownMenu.RadioItem

  • value Required
    string | (string & string[])

    Value this item selects.

  • children
    Snippet<[]>

    Content rendered inside.

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the DOM element.

  • class
    (string & (ClassValue | null))

    Classes added to the element.

DropdownMenu.Separator

Separates menu items.

  • children
    Snippet<[]>

    Content rendered inside.

  • class
    string

    Classes added to the element.

DropdownMenu.Sub

Provides submenu state.

  • children
    Snippet<[]>

    Content rendered inside.

DropdownMenu.SubContent

Renders a submenu surface.

  • surface
    "solid" | "glass"

    Surface treatment. Omit it to inherit --mielui-surface from the theme.

  • children Required
    Snippet<[]>

    Content rendered inside.

  • class
    string

    Classes added to the element.

  • dynamic
    boolean

    Lets the panel grow to fit its content instead of matching the trigger width.

    Default false

DropdownMenu.SubTrigger

Opens a submenu.

  • class
    string

    Additional classes for the element.

  • children
    Snippet<[]>

    Content rendered inside.

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the DOM element.

  • onclick
    ((event: MouseEvent) => void)

    Called when it is clicked or activated.

  • onopen
    (() => void)

    Called immediately before this trigger opens its submenu.

  • style
    string

    Inline styles for the element.

DropdownMenu.Trigger

Opens the dropdown menu.

  • children Required
    Snippet<[]>

    Content rendered inside.

  • class
    string

    Classes added to the element.

  • variant
    ButtonVariant

    Button style of the trigger.

  • style
    string

    Inline styles for the element.

  • onclick
    ((event: MouseEvent) => void)

    Called when it is clicked or activated.

  • size
    "sm" | "md" | "lg" | "icon"

    Control height of the trigger.

  • element Bindable
    HTMLButtonElement | HTMLAnchorElement

    Bindable reference to the DOM element.

  • unstyled
    boolean

    Forwarded to Button: render with class alone, skipping variant/size.

  • icon
    boolean

    Shows the chevron after the label.

    Default false

  • onopen
    (() => void)

    Called immediately before this trigger opens its popover.