Dropdown menu
Lists the actions on one thing.
Items
actions, destructive lastdefault
labels, checkbox and radio
submenu
Sizes
defaultdefault
sm
States
highlighted
destructive highlighted
disabled
checked
Usage
When to use it
- Use
- Three or more actions on one item would crowd its row.
- Use
- A row where people expect a right click, with the same actions on a visible More button.
- Use
- A few view settings that toggle: columns, sort, density.
- Not for
- One or two actions; show them. Use Button.
- Not for
- Finding one of many commands by name. Use Command.
- Not for
- A few lines of content or a small form. Use Popover.
Rules
8 rules
- Must
- Put a destructive item last, after a DropdownMenuSeparator, with variant="destructive". Because a destructive item first sits under the pointer and the first arrow press, one slip from Delete.
- Never
- Colour an item by className; use variant="destructive". Because a hand-set colour skips the fail tint on highlight and the dark theme's coral.
- Must
- Put the same actions behind a visible More button on every row a contextmenu trigger covers. Because a right click or a long press is invisible, so a person who never tries one never finds the actions.
- Must
- Use DropdownMenuLinkItem for an item that navigates; never an item with router.push. Because router.push leaves a menu item with no href, so open in a new tab and copy link do nothing.
- Must
- Name an icon trigger with the object it acts on. Because every row's trigger reads "More", so a screen reader cannot tell which row it opens.
- Should
- Pass size="sm" to DropdownMenuContent opened from a small control or a row, so 28px rows match the 26px trigger.
- Should
- Start each item with a verb in sentence case, naming the object when the menu covers several.
- Never
- Add a shadow to DropdownMenuContent; the ring on paper is its edge. Because Glaze has no shadows, and every shadow class draws nothing here.
State order
4 states, highest first
- 1
- disabled
- 2
- highlighted
- 3
- checked
- 4
- rest
Accessibility
5 checks
- The trigger carries aria-haspopup="menu" and aria-expanded; the popup is role="menu".
- The highlighted row's wash is the focus indicator; forced colors draw a 2px outline on 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 the row's 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 panel, which stops at the available width.
- Forty items
- The panel 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 in a toast 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 keysstring[]DropdownMenuCheckboxItem, RadioItemBase UI
Tokens
22 tokens
--paper- #ffffff
--line- rgb(5 5 5 / 0.10)
--line-faint- rgb(5 5 5 / 0.06)
--radius-overlay- 10px
--radius-tag- 6px
--menu-min- 192px
--menu-row- 32px
--menu-row-sm- 28px
--popup-max- 320px
--hover- rgb(5 5 5 / 0.06)
--pressed- rgb(5 5 5 / 0.10)
--fail-tint- #fbe9e5
--fail-text- #b23c29
--type-label- 500 13px/1.35 var(--font-sans)
--type-label-sm- 500 12px/1.35 var(--font-sans)
--type-caption- 400 12px/1.35 var(--font-sans)
--type-code-xs- 400 calc(12px * var(--mono-scale))/1.35 var(--font-mono)
--text-1- rgb(5 5 5 / 0.80)
--text-2- rgb(5 5 5 / 0.60)
--text-3- rgb(5 5 5 / 0.45)
--text-disabled- rgb(5 5 5 / 0.32)
--ink- #050505