gray
radius
Overlays

Dropdown menu

Actions from a trigger. A menu is not a styled list: it has roving focus, typeahead, and a role a screen reader recognises.

Basic

Open it and start typing. Focus jumps to the matching item.

<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button variant="secondary">
Actions
<Icon name="expand_more" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Label>Package</DropdownMenu.Label>
<DropdownMenu.Item>Rename</DropdownMenu.Item>
<DropdownMenu.Item>Deprecate</DropdownMenu.Item>
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger>Transfer to…</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent>
<DropdownMenu.Item>Another user</DropdownMenu.Item>
<DropdownMenu.Item>An organisation</DropdownMenu.Item>
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
<DropdownMenu.Separator />
<DropdownMenu.Item destructive>Unpublish</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
Typeahead, arrow keys, Escape and outside-click dismissal all come from the base layer. All of them are things a hand-rolled menu ships without.

Checkbox and radio items

These keep a gutter for the indicator, so the list does not shift sideways when one becomes selected.

<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Button variant="secondary">
Publish options
<Icon name="expand_more" />
</Button>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.CheckboxItem checked={dryRun} onCheckedChange={setDryRun}>
Dry run
</DropdownMenu.CheckboxItem>
<DropdownMenu.Separator />
<DropdownMenu.Label>Tag</DropdownMenu.Label>
<DropdownMenu.RadioGroup value={tag} onValueChange={setTag}>
<DropdownMenu.RadioItem value="latest">latest</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="next">next</DropdownMenu.RadioItem>
<DropdownMenu.RadioItem value="canary">canary</DropdownMenu.RadioItem>
</DropdownMenu.RadioGroup>
</DropdownMenu.Content>
</DropdownMenu.Root>
destructive gives a row a red label, and fills it red once it is highlighted. Color alone does not carry that meaning, so the label still has to say “Unpublish”.

Parts

Import it as a namespace, and the parts work on either side of the server boundary.

import * as DropdownMenu from '@minuk-hwang-design-system/components-react/dropdown-menu';
PartWhat it is
DropdownMenu.RootOwns the open state.
DropdownMenu.TriggerGets role and aria-expanded.
DropdownMenu.ContentThe menu. Portalled, flips near an edge.
DropdownMenu.ItemAn action.
DropdownMenu.CheckboxItemToggles, with an indicator gutter.
DropdownMenu.RadioGroup / RadioItemOne of a set.
DropdownMenu.LabelGroup heading. Skipped by the keyboard.
DropdownMenu.SeparatorDivider.
DropdownMenu.Sub / SubTrigger / SubContentNested menu.

Props

PropTypeDefaultNotes
destructivebooleanfalseOn Item. Red label, filled red when highlighted; the label still carries the meaning.
onSelect(event) => voidOn Item. Fires for click and Enter alike.
side / alignstringOn Content. Preferred placement.