Dropdown menu
A list of actions or options opened from a button. Full keyboard support from Radix: arrows, typeahead, submenus, Esc. Source: src/components/ui/dropdown-menu.tsx.
Groups, shortcuts and submenus
A label, grouped items with Tabler icons, Kbd shortcuts, a submenu, a disabled row and a destructive item set apart at the end.
Checkbox and radio items
Checkbox items toggle independently; radio items pick one. Preventing onSelect keeps the menu open while several columns are toggled.
Custom indicator
indicator="none" removes the gutter so a richer row can show its own state, as the composer's model pill does.
Row actions
The overflow menu on a list row: an icon-sm ghost trigger with a specific aria-label, aligned to the end so it opens inward.
Keyboard
| Key | Where | Does |
|---|---|---|
| Enter · Space | Open and focus the first item. ↓ opens too; ↑ opens on the last item. | |
| ↑ ↓ | Move between items, skipping disabled ones. Typing a letter jumps to a match. | |
| → ← | Open the submenu and enter it / close it and return. | |
| Enter · Space | Select. Checkbox and radio items toggle. | |
| Esc | Close and return focus to the trigger. |
Motion and layering
Asymmetric enter and exit on keyframes; under reduced motion the global rule cuts both to near-zero. Item highlight is a colour change only, so nothing shifts.
| Value | Token | Usage |
|---|---|---|
| 50 | Above the page, same layer as dialogs. The brand ladder (--z-dropdown 100) is defined but not wired in yet. | |
| 140ms | Fade, scale from 95% out of the trigger, 8px slide. | |
| 90ms | Fade and scale out, faster than the entrance. | |
| 100ms | Item highlight (tint-10 fill) as focus moves. | |
| max-h | Long menus scroll inside the viewport instead of overflowing it. |
Props
| Part / prop | Type | Default | Description |
|---|---|---|---|
| DropdownMenu | Radix DropdownMenu.Root | — | open, defaultOpen, onOpenChange, modal (true: outside clicks are blocked while open). |
| DropdownMenuTrigger | Radix DropdownMenu.Trigger | — | Use asChild around a Button. Gets aria-expanded, which Button styles. |
| DropdownMenuContent | Radix DropdownMenu.Content | — | Portals itself. Takes side, align, loop. A pointer close leaves focus where it is; a keyboard close returns it to the trigger. |
| sideOffset | number | 4 | Gap from the trigger, in px. |
| DropdownMenuItem | Radix DropdownMenu.Item | — | onSelect, disabled, textValue (for typeahead). |
| variant | "default" | "destructive" | "default" | Destructive turns text, icon and focus fill red. |
| inset | boolean | false | 32px left padding to line up with iconed or checkable rows. Also on Label and SubTrigger. |
| DropdownMenuCheckboxItem | Radix CheckboxItem | — | checked, onCheckedChange. role=menuitemcheckbox. |
| indicator | "check" | "none" | "check" | none drops the check gutter so the row shows its own state. |
| DropdownMenuRadioGroup | Radix RadioGroup | — | value, onValueChange. |
| DropdownMenuRadioItem | Radix RadioItem | — | value. role=menuitemradio. |
| indicator | "dot" | "none" | "dot" | none drops the dot gutter. |
| DropdownMenuLabel | Radix Label | — | A non-interactive heading row; inset available. |
| DropdownMenuGroup, Separator | Radix | — | Group related items; the separator is a hairline that bleeds to the edges. |
| DropdownMenuShortcut | ComponentProps<"span"> | — | Right-aligned slot. Holds plain text or Kbd. Display only; it binds nothing. |
| DropdownMenuSub, SubTrigger, SubContent | Radix | — | Nested menu. SubContent renders inline (no portal). |
| DropdownMenuPortal | Radix Portal | — | Exported for custom shells; Content already portals. |
Best practices
When to use
- Actions or a single choice behind one button. A form value with a visible current state is a Select.
- For the same actions on right-click, mirror them in a ContextMenu; the item styles are shared.
- Search across many commands belongs in Command, not a long dropdown.
Behavior
- Group with separators; put destructive items last, on their own.
- Shortcuts are display only. Bind the key elsewhere and show the same combo here.
- Keep submenus one level deep.
Accessibility
- Icon-only triggers need an aria-label that names the object (“Actions for …”).
- Icons inside items are aria-hidden; the text is the label.
- Keyboard closes return focus to the trigger; pointer closes deliberately don't, to avoid a stray ring and tooltip.
Content
- Verbs for actions (Rename, Delete), nouns for options (Name, Cost).
- Explain a disabled item in its label, or remove it.