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) => void
DropdownMenuTrigger selectable
boolean default false
DropdownMenuContent size
'default' | 'sm' default 'default'
DropdownMenuItem variant
'default' | 'destructive' default 'default'
DropdownMenuItem inset
boolean default false
DropdownMenuShortcut keys
string[]
DropdownMenuCheckboxItem, RadioItem
Base 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

Install

For your coding agent

GitHub

Please star me pookie

141

A new design system every week

Leave your email before you copy the prompt.