Dropdown menu
Action menu anchored to a trigger. Click to open, Esc or click-outside to close.
Dropdown menu opens a list from a button: actions, checkbox and radio rows, labels, separators, shortcuts, and submenus. A row that holds the current value marks it with a check on the right, the way Select and Combobox do, and checkbox rows stay open while you toggle them. Menu is this menu with icon, shortcut, and destructive props on its items, and Context menu opens the same rows on right click.
Usage
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>File</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onSelect={createFile}>New</DropdownMenuItem>
<DropdownMenuItem onSelect={saveFile}>Save</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>Pair the trigger with a Button through asChild, so the
trigger is the button and keeps its look. A row runs its action in onSelect, which fires
on a click, Enter, or Space, and then the menu closes. The panel opens 4px from the trigger
on bg-surface-raised with a thin ring-border-strong edge, in font-sans, and the focused row
takes the bg-fill wash. It fades in and out without sliding, so the motion does not fight
its placement.
Rows line up with or without an icon
An icon placed in a plain row or a submenu trigger is drawn at 16px with a gap before the
label, so <DropdownMenuItem><PencilSimple />Edit</DropdownMenuItem> needs no classes.
inset pads the left edge of a row, a label, or a submenu trigger by the width of that icon
and its gap, so one without an icon lines its text up with the rows that have one.
A submenu trigger draws its own caret on the right and keeps the wash while its submenu is
open. Submenus can nest, and one level is usually enough to keep a menu easy to scan.
DropdownMenuShortcut sets keys like ⌘E at the end of a row in small, faded text. It only
shows them, so bind the shortcut yourself where the action lives.
The current value is a check on the right
A checkbox or radio row marks the current value with a check in a gutter on the right, which
is how Select and
Combobox read too. Radio rows use the same check rather
than a filled dot, so one mark means "this is current" in every picker, and the row's role
tells assistive tech whether it is one choice or several (menuitemradio or
menuitemcheckbox).
The label starts at the same left edge as a plain row, so a menu that mixes plain and checkable rows does not indent half of them. The gutter is there whether or not the row is checked, so checking a row never reflows the menu.
Checkbox rows stay open
A checkbox row keeps the menu open when you toggle it, so a list of filters or columns can
be set in one visit instead of reopening the menu for each one. Toggle the columns above to
see it. Pass closeOnSelect to a checkbox row that should close the menu. Radio rows and
plain rows close it when picked.
Modal or not
modal is on by default. While the menu is open the rest of the page ignores the pointer,
does not scroll, and is hidden from assistive tech, and a click outside only closes the menu.
With modal={false} the page stays live, and a click outside closes the menu and lands on
whatever it hit.
Props
Every part takes the props of its Radix part, as on Radix DropdownMenu, plus these.
| Prop | Type | Default | What it does |
|---|---|---|---|
inset | boolean | On DropdownMenuItem, DropdownMenuSubTrigger, and DropdownMenuLabel. Pads the left edge so the text starts where an icon row's label does. | |
closeOnSelect | boolean | false | On DropdownMenuCheckboxItem. Closes the menu when the row is toggled. Off, the menu stays open. |
sideOffset | number | 4 | On DropdownMenuContent. The gap between the trigger and the panel, in pixels. |
Accessibility
- The trigger is a
<button>witharia-haspopup="menu"andaria-expanded, and the open menu takes the trigger's label as its name. Enter, Space, or ↓ opens it and focuses the first row. - In the menu, ↑ and ↓ move between rows and stop at the ends, Home and End jump to the first and last, and typing a letter moves to the next row that starts with it. Enter or Space picks the focused row.
- On a submenu trigger, →, Enter, or Space opens the submenu and moves into it, and ← closes it and puts focus back on the submenu trigger.
- Escape closes the whole menu, from a submenu too, and focus goes back to the trigger.
- Rows are
menuitem, checkbox rows aremenuitemcheckboxand radio rows aremenuitemradio, each witharia-checked. Groups aregroupand separators areseparator. DropdownMenuLabelis plain text that the arrow keys skip, and it is not tied to the group under it, so a screen reader moving through the rows does not hear it. Give theDropdownMenuGroupanaria-labelwhen the heading matters.- A row's name is all of its text, so a shortcut is read as part of it, and a Phosphor icon adds nothing.
- A
disabledrow setsaria-disabled, drops to 64% opacity, and is skipped by the arrow keys and typeahead. - With
modalon, the page behind the open menu is hidden from assistive tech. - Rows are 32px tall and do not grow on a touch screen.
- Under reduced motion the panel opens and closes without the fade, and rows change color without a transition.
- The highlight is only a background wash, which forced colors repaints, so in Windows high contrast the focused row has no mark of its own.
When to use something else
- Menu when every row is an action with an optional icon, shortcut, or destructive style. It is this menu with those props on its items, so the two mix.
- Context menu for the same rows opened by right click or a long press on the thing they act on, rather than from a button.
- Select to pick one value in a form, and Combobox when the list is long enough to search. This panel grows with its rows and does not scroll, so a list that could run off the screen belongs there.
- Command when there are enough actions that typing to find one helps.
- Filter chip for a filter in a toolbar. It is built on this menu and shows the current choice on the chip.
Install
shadcn add @gesso/dropdown-menu