Overlays & navigation
Dropdown menu
A dropdown menu lists actions behind a button: account menus, row actions, view options. Items take icons, shortcuts, checkboxes, radio choices, section labels, separators, a danger style and nested submenus. It follows the WAI-ARIA menu button pattern, including arrow keys, Home and End, typeahead and Escape, and renders in the top layer so it is never clipped.
On this page
<DropdownMenu label="Account" className="w-60" trigger={ <Button variant="ghost" iconRight={<ChevronDown strokeWidth={1.75} />} className="pl-1.5"> <Avatar name="Ada Moreno" size="sm" decorative /> Ada Moreno </Button> } items={[ { type: "label", label: workspace }, { label: "Profile", icon: <User strokeWidth={1.75} />, shortcut: "⇧⌘P" }, { label: "Billing", icon: <CreditCard strokeWidth={1.75} />, shortcut: "⌘B" }, { label: "Settings", icon: <Settings strokeWidth={1.75} />, shortcut: "⌘," }, { label: "Keyboard shortcuts", icon: <Keyboard strokeWidth={1.75} />, shortcut: "?" }, { type: "separator" }, { type: "submenu", label: "Switch workspace", icon: <Users strokeWidth={1.75} />, items: [ ...workspaces.map((w) => ({ type: "radio" as const, label: w, checked: w === workspace, onSelect: () => setWorkspace(w) })), { type: "separator" as const }, { label: "New workspace", icon: <Plus strokeWidth={1.75} /> }, ], }, { label: "Invite people", icon: <Plus strokeWidth={1.75} />, disabled: true }, { type: "separator" }, { label: "Log out", icon: <LogOut strokeWidth={1.75} />, shortcut: "⇧⌘Q" }, ]}/>Usage
import { DropdownMenu } from "@/components/ui/dropdown-menu"; <DropdownMenu trigger={<Button>Actions</Button>} items={[ { label: "Edit", icon: <Pencil strokeWidth={1.75} />, onSelect: edit }, { type: "separator" }, { label: "Delete", icon: <Trash2 strokeWidth={1.75} />, danger: true, onSelect: remove }, ]}/>Examples
Account menu
Section label, icons, shortcuts, a disabled item and a submenu with radio items. Hover or press ArrowRight on “Switch workspace”.
<DropdownMenu label="Account" className="w-60" trigger={ <Button variant="ghost" iconRight={<ChevronDown strokeWidth={1.75} />} className="pl-1.5"> <Avatar name="Ada Moreno" size="sm" decorative /> Ada Moreno </Button> } items={[ { type: "label", label: workspace }, { label: "Profile", icon: <User strokeWidth={1.75} />, shortcut: "⇧⌘P" }, { label: "Billing", icon: <CreditCard strokeWidth={1.75} />, shortcut: "⌘B" }, { label: "Settings", icon: <Settings strokeWidth={1.75} />, shortcut: "⌘," }, { label: "Keyboard shortcuts", icon: <Keyboard strokeWidth={1.75} />, shortcut: "?" }, { type: "separator" }, { type: "submenu", label: "Switch workspace", icon: <Users strokeWidth={1.75} />, items: [ ...workspaces.map((w) => ({ type: "radio" as const, label: w, checked: w === workspace, onSelect: () => setWorkspace(w) })), { type: "separator" as const }, { label: "New workspace", icon: <Plus strokeWidth={1.75} /> }, ], }, { label: "Invite people", icon: <Plus strokeWidth={1.75} />, disabled: true }, { type: "separator" }, { label: "Log out", icon: <LogOut strokeWidth={1.75} />, shortcut: "⇧⌘Q" }, ]}/>Row actions with a danger item
An icon button trigger aligned to the end of a row. Put destructive actions last, after a separator.
INV-2041
Fernhill Studio · €4,200.00
<DropdownMenu label="Invoice actions" align="end" trigger={ <Button size="icon-sm" variant="ghost" aria-label="Invoice actions"> <Ellipsis strokeWidth={1.75} /> </Button> } items={[ { label: "Edit", icon: <Pencil strokeWidth={1.75} />, shortcut: "E", onSelect: act("Edit") }, { label: "Duplicate", icon: <Copy strokeWidth={1.75} />, shortcut: "⌘D", onSelect: act("Duplicate") }, { label: "Archive", icon: <Archive strokeWidth={1.75} />, onSelect: act("Archive") }, { type: "separator" }, { label: "Delete", icon: <Trash2 strokeWidth={1.75} />, shortcut: "⌘⌫", danger: true, onSelect: act("Delete") }, ]}/>Checkbox and radio items
Checkbox and radio items keep the menu open, so several options can be changed in one go.
<DropdownMenu label="View options" trigger={<Button iconLeft={<SlidersHorizontal strokeWidth={1.75} />}>View</Button>} items={[ { type: "label", label: "Show" }, { type: "checkbox", label: "Sidebar", checked: show.sidebar, onCheckedChange: toggle("sidebar"), shortcut: "⌘\\" }, { type: "checkbox", label: "Status bar", checked: show.status, onCheckedChange: toggle("status") }, { type: "checkbox", label: "Compact rows", checked: show.compact, onCheckedChange: toggle("compact") }, { type: "separator" }, { type: "label", label: "Sort by" }, ...["Date", "Amount", "Client"].map((s) => ({ type: "radio" as const, label: s, checked: sort === s, onSelect: () => setSort(s) })), ]}/>Compact icon menu
Menu is the shorthand for the common “more” button: pass the icon and a label. It is built on DropdownMenu.
<Menu label="More actions" trigger={<Ellipsis strokeWidth={1.75} />} items={[ { label: "Rename", icon: <Pencil strokeWidth={1.75} /> }, { label: "Duplicate", icon: <Copy strokeWidth={1.75} /> }, { label: "Delete", icon: <Trash2 strokeWidth={1.75} />, danger: true }, ]}/>Props
DropdownMenu
triggerrequiredReactNodedefault: —Your button element (gets aria-haspopup, aria-expanded, aria-controls) or text for a default button.itemsrequiredDropdownMenuItem[]default: —The entries, in order. See the item types below.labelstringdefault: —Accessible name of the menu. Defaults to the trigger text when it is a string.placement"top" | "bottom" | "start" | "end"default: "bottom"Preferred side of the trigger.align"start" | "center" | "end"default: "start"Edge of the trigger the menu lines up with.classNamestringdefault: —Extra classes for the panel, e.g. a width (default min width 208px).onOpenChange(open: boolean) => voiddefault: —Called when the menu opens or closes.
DropdownMenuItem
item{ label, icon?, shortcut?, disabled?, danger?, href?, onSelect? }default: —An action. href renders a link. Selecting closes the menu.checkbox{ type: "checkbox", label, checked, onCheckedChange }default: —Toggle. Keeps the menu open.radio{ type: "radio", label, checked, onSelect }default: —One of a set. Keeps the menu open.submenu{ type: "submenu", label, icon?, items }default: —Opens a nested menu to the side.label{ type: "label", label }default: —Small heading for a section.separator{ type: "separator" }default: —A hairline between groups.
Menu
triggerrequiredReactNodedefault: —The icon shown in the 32px button.labelrequiredstringdefault: —Accessible name of the button and the menu.itemsrequired{ label, icon?, danger?, onSelect? }[]default: —Plain actions.align"start" | "end"default: "end"Edge the menu lines up with.
Accessibility
- The trigger has aria-haspopup="menu" and aria-expanded. Enter, Space and ArrowDown open the menu on the first item, ArrowUp on the last.
- Inside, ArrowUp and ArrowDown move between items (wrapping), Home and End jump to the ends, and typing letters jumps to the matching item.
- Escape closes the menu and returns focus to the trigger. Tab closes it and moves on.
- Submenus open with ArrowRight, Enter or Space and close with ArrowLeft or Escape, returning focus to their parent item.
- Items use role menuitem, menuitemcheckbox or menuitemradio with aria-checked; disabled items are skipped and marked aria-disabled.