Gesso
Actions

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 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.

PropTypeDefaultWhat it does
insetbooleanOn DropdownMenuItem, DropdownMenuSubTrigger, and DropdownMenuLabel. Pads the left edge so the text starts where an icon row's label does.
closeOnSelectbooleanfalseOn DropdownMenuCheckboxItem. Closes the menu when the row is toggled. Off, the menu stays open.
sideOffsetnumber4On DropdownMenuContent. The gap between the trigger and the panel, in pixels.

Accessibility

  • The trigger is a <button> with aria-haspopup="menu" and aria-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 are menuitemcheckbox and radio rows are menuitemradio, each with aria-checked. Groups are group and separators are separator.
  • DropdownMenuLabel is 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 the DropdownMenuGroup an aria-label when 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 disabled row sets aria-disabled, drops to 64% opacity, and is skipped by the arrow keys and typeahead.
  • With modal on, 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

On this page