Dropdown Menu
A list of actions hung off a button — the row overflow, the account menu, the sort control.
Installation
npx shadcn@latest add https://qure-ui.qure.ai/r/dropdown-menu.jsonUsage
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="tertiary">Actions</Button>} />
<DropdownMenuContent>
<DropdownMenuItem>Open report</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">Remove from worklist</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>A menu holds actions — things that happen. A Select holds values — things that are chosen and
then persist in the trigger. If, after clicking, the button should show what you picked, you
wanted a select.
Composition
<DropdownMenu> {/* owns the open state */}
<DropdownMenuTrigger> {/* the button, and the anchor */}
<DropdownMenuContent> {/* portal + positioner + popup */}
<DropdownMenuGroup> {/* a section of related items */}
<DropdownMenuLabel> {/* its heading; not an item, not focusable */}
<DropdownMenuItem> {/* an action */}
<DropdownMenuItemText> {/* label column, when there is a description */}
<DropdownMenuDescription> {/* the second line */}
<DropdownMenuShortcut> {/* the key hint, pushed to the trailing edge */}
</DropdownMenuGroup>
<DropdownMenuSeparator> {/* a rule, full bleed to the popup's edges */}
<DropdownMenuCheckboxItem> {/* a setting that toggles; keeps the menu open */}
<DropdownMenuRadioGroup> {/* one-of-many, with DropdownMenuRadioItem */}
<DropdownMenuSub> {/* a nested menu */}
<DropdownMenuSubTrigger> {/* the row that opens it; renders its own chevron */}
<DropdownMenuSubContent> {/* the nested popup, to the right by default */}DropdownMenuItemText exists for one reason: it puts the description under the label rather than
under the icon, which is how the Figma item is drawn. An item with no description does not need
it — put the label straight into DropdownMenuItem.
DropdownMenuLabel is Base UI's Menu.GroupLabel, and it throws unless it is inside a
DropdownMenuGroup or a DropdownMenuRadioGroup — "MenuGroupContext is missing" at runtime,
not at compile time. A heading in a menu names a group, so put the items it names inside the
group with it.
Actions on a worklist row
The overflow menu behind a … button. Destructive items take variant="destructive" and go
below a separator, so that the muscle memory for "second from the bottom" never lands on delete.
Items with descriptions
Where the consequence of an action is not obvious from three words, spend the second line. "Locks the report. An addendum is the only way back" is the difference between an informed click and a support ticket.
Toggling settings
DropdownMenuCheckboxItem defaults to closeOnClick={false}, so four columns can be toggled
without reopening the menu four times.
One of several
A radio group inside a menu is the right shape for a sort order or a view mode: exactly one is active, and the menu is the only place it is set.
Submenus
Nest one level, and only when the second level is a genuine sublist — a roster, a set of priors. Two levels of nesting in a menu is a navigation tree wearing a disguise.
How highlight works
Base UI puts data-highlighted on the active row, whether it got there by pointer or by arrow
key, and keeps only one row lit at a time. Styling :hover instead is the classic bug: the
keyboard user sees nothing move.
.cn-dropdown-menu-item[data-highlighted] {
background-color: var(--background-brand-default);
color: var(--text-brand-on-brand);
}The full teal fill is Figma's hover state for Menu Item (node 48:20821), not a liberty.
The trigger must be a real button. DropdownMenuTrigger renders one by default; if you replace
it with something else through render, pass nativeButton={false} so Base UI wires up the
keyboard handling it can no longer assume.
Keyboard
Enter, Space or Down opens the menu and lands on the first item. Arrows move, typing jumps to a matching label, Right opens a submenu and Left leaves it, Escape closes and returns focus to the trigger.
API Reference
Everything Base UI's Menu accepts. What we add on
DropdownMenuItem:
Prop
Type
On DropdownMenuContent:
Prop
Type
Worth knowing, from the primitive:
| Prop | Where | Note |
|---|---|---|
closeOnClick | Item | true on actions, false on checkbox and radio items. |
modal | Root | true by default: the page behind is inert while the menu is open. |
openOnHover | Trigger, SubmenuTrigger | Off for the root trigger, and best left off. |
loopFocus | Root | true; arrowing past the last item returns to the first. |