Dropdown menu
Lists the actions on one thing.
Items
actions, destructive lastdefault
labels, checkbox and radio
submenu
Sizes
defaultdefault
sm
Trigger
contextmenu
States
highlighted
destructive highlighted
disabled
checked
Usage
When to use it
- Use
- More actions on one thing than fit beside it: a job's replay, copy, open and cancel.
- Use
- A row's actions behind its More button, and the same list on a right click.
- Use
- View options for a table: which columns, which sort.
- Not for
- The person picks a value for a field. Use Select.
- Not for
- The person finds an action by typing. Use Command.
- Not for
- A few lines about the thing, not actions. Use Popover.
Rules
7 rules
- Must
- Start each item with a verb in sentence case, naming the object when the menu covers several; the row sets the capitals. Because an item is an action, and a label like "ID" does not say what pressing it does.
- Must
- Put a destructive item last, after a separator, with variant="destructive". Because the failure word and its tint mark the one item that loses work, and the rule keeps a slip of the arrow off it.
- Must
- Use DropdownMenuLinkItem for an item that goes to another page. Because a real link opens in a new tab and copies its address, which an onClick navigation cannot.
- Must
- Give every context menu a visible path too, usually the row's More button with the same items. Because a right click is invisible until tried, and a keyboard or touch user may never find it.
- Should
- Write a shortcut as keys="mod+c" on DropdownMenuShortcut. Because Kbd writes it for the reader's platform, Command on a Mac and Ctrl elsewhere.
- Never
- Fill a checked or chosen item with the selection wash or any colour; its ink check is the mark. Because three ticked rows in a seven-row menu became three yellow bands, which reads as noise, not state; the wash belongs to a selected table row.
- Never
- Add a shadow, padding or a rounded corner to DropdownMenuContent. Because Sodium has no shadows, only rules, and its rows run edge to edge so a highlighted row's lift and hairlines meet the edge.
State order
4 states, highest first
- 1
- disabled
- 2
- highlighted
- 3
- checked
- 4
- rest
Accessibility
6 checks
- The trigger carries aria-haspopup="menu" and aria-expanded; the popup is role="menu".
- The highlighted row lifts to white off the panel between two --line hairlines, in text-1 (18.3:1 and 13:1); that lift is the focus indicator, and forced colors draw a 2px outline.
- A checked item shows an ink check in a fixed leading column and no fill, so unchecked items line up with it.
- A disabled item stays reachable by arrows so its label is read; Enter does nothing.
- Checkbox and radio items are menuitemcheckbox and menuitemradio with aria-checked.
- A context menu takes its name through aria-label, since it has no trigger button.
Keyboard
5 keys
- Enter, Space, Down
- On the trigger: opens with the first item highlighted.
- Up, Down, a letter
- Moves the highlight; a letter jumps to the next item starting with it.
- Right, Left
- Opens and closes a submenu, mirrored in right to left.
- Shift+F10
- Opens a context menu at the focused row.
- Escape
- Closes this menu only and returns focus to the trigger.
Edge cases
6 edge cases
- Long item label
- Wraps inside the popup, which stops at the available width.
- Forty items
- The popup scrolls past 320px; group them with labels, or use a Command.
- Near a viewport edge
- It flips or shifts to stay 8px inside.
- Touch
- A long press opens the context menu without selecting a word.
- Failed action
- Report it after the menu closes; the menu has no error state.
- Reduced motion
- It cuts in and out.
API
8 props
DropdownMenu trigger'click' | 'contextmenu'default'click'DropdownMenu onTargetChange(row: HTMLElement | null) => voidDropdownMenuTrigger selectablebooleandefaultfalseDropdownMenuContent size'default' | 'sm'default'default'DropdownMenuItem variant'default' | 'destructive'default'default'DropdownMenuItem insetbooleandefaultfalseDropdownMenuShortcut keysstringDropdownMenuCheckboxItem, RadioItemBase UI
Tokens
23 tokens
--panel- #fafaf7
--line- #e2dfd5
--row-highlight- #ffffff
--row-highlight-edge- var(--line)
--field-inset- 10px
--field-inset-sm- 7px
--menu-min- 192px
--popup-max- 320px
--row-sm- 32px
--control-sm- 26px
--selected- #fff4bf
--fail-tint- #fdecea
--fail-text- #b3261e
--type-label- 400 13px/16px var(--font-mono)
--type-label-sm- 400 12px/16px var(--font-mono)
--type-micro- 400 11px/14px var(--font-mono)
--text-1- #161512
--text-3- #6b675e
--text-disabled- #a8a499
--icon- 16px
--z-overlay- 50
--dur-overlay- 160ms
--dur-state- 120ms