Skip to content

Dropdown Menu

Stable

A list of actions or options that appears from a trigger, keeping secondary commands one click away without cluttering the interface.

Installation

$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/dropdown-menu.json

Usage

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
<DropdownMenu>
  <DropdownMenuTrigger render={<Button variant="outline" />}>
    Options
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuGroup>
      <DropdownMenuLabel>My account</DropdownMenuLabel>
      <DropdownMenuItem>
        Profile
        <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
      </DropdownMenuItem>
      <DropdownMenuItem>Settings</DropdownMenuItem>
    </DropdownMenuGroup>
    <DropdownMenuSeparator />
    <DropdownMenuGroup>
      <DropdownMenuItem>Log out</DropdownMenuItem>
    </DropdownMenuGroup>
  </DropdownMenuContent>
</DropdownMenu>

The trigger renders any element through render — usually a Button. Items always live inside a DropdownMenuGroup, so screen readers announce the group label and separators have something to separate.

Examples

Account menu

The most common menu: who you are at the top, account actions in the middle, and the way out last. Shortcuts sit on the right edge in a quieter color so they're scannable without competing with labels.

Checkbox items

Use DropdownMenuCheckboxItem for independent on/off settings such as visible columns. Checkbox items stay open when clicked (closeOnClick defaults to false), so people can toggle several in one visit.

Radio group

DropdownMenuRadioGroup holds mutually exclusive choices — sort order, density, a view mode. Reflect the current choice in the trigger's label so the state is visible while the menu is closed.

Nest related actions with DropdownMenuSub. Submenus open on hover after a short delay and with from the keyboard. Keep nesting to one level — a second level is a sign the menu is doing a page's job.

Row actions

A ghost icon button with a glyph is the conventional home for per-item actions in lists and tables. Align the menu to the trigger's end (align="end") so it opens toward the content, and give the trigger an aria-label that names the item it acts on.

Quarterly planningEdited 2 hours ago
Active

Disabled items

Disable an item when the action exists but is temporarily unavailable, and say why in the label. If an action never applies in the current context, remove it instead.

Guidelines

When to use

  • To collect secondary actions behind a single trigger: row actions, overflow toolbars, account menus.
  • To offer a small set of view settings — toggles and exclusive choices — without a settings page.

When not to use

  • To choose a value that becomes part of a form — use a Select or Combobox.
  • For the primary action of a view — keep it visible as a Button.
  • For site or app navigation with rich panels — use a Navigation Menu.
  • For actions on an area, discovered by right-click — use a Context Menu, and always offer the same actions somewhere visible.

Item content

Start every item with a verb or a noun that names the destination: Rename, Duplicate, Settings. Use sentence case, keep items to one line, and append an ellipsis (…) only when the item opens a dialog that asks for more input before anything happens.

Icons are optional, but all-or-nothing within a group: a column of labels where some have icons and some don't looks broken. Use inset on unaccompanied items when the rest of the menu has icons or indicators.

Rename
Duplicate
Archive
Delete
Do.Verbs up front, consistent icons, a single destructive item at the end.
Name change
Delete forever
Make a copy of this
Choose a country…
Don’t.Mixed phrasing, a buried destructive action, and a menu doing a form's job.

Destructive actions

Put destructive items last, separated from the rest, and use variant="destructive". If the action can't be undone, the item should open an Alert Dialog rather than act immediately. Never make a destructive item the first thing keyboard focus lands on.

Shortcuts

DropdownMenuShortcut is a hint, not a binding — register the actual keyboard handler elsewhere. Show shortcuts only for actions that really have one, use the platform's symbols (⌘ ⇧ ⌥ ⌃ ⌫ ↵) in their conventional order (⌃ ⌥ ⇧ ⌘), and don't show a shortcut for an item that is disabled.

Size and grouping

Seven to ten items is a comfortable maximum. Group related items and separate groups with DropdownMenuSeparator; label groups with DropdownMenuLabel when their purpose isn't obvious. Menus are at least as wide as their trigger, never truncate labels, and grow to fit their longest item.

Accessibility

Dropdown Menu follows the WAI-ARIA menu button pattern through Base UI. The trigger gets aria-haspopup and aria-expanded; items get the menuitem, menuitemcheckbox or menuitemradio role; focus moves into the menu on open and returns to the trigger on close.

KeyBehavior
EnterSpace
On the trigger, opens the menu and focuses the first item.
On the trigger, opens the menu and focuses the last item.
Moves focus between items. Focus loops from the last item to the first.
HomeEnd
Focuses the first or last item.
On a submenu trigger, opens the submenu and focuses its first item.
In a submenu, closes it and returns focus to its trigger.
EnterSpace
Activates the focused item. Checkbox and radio items toggle without closing.
A–Z
Typeahead: focuses the next item whose label starts with the typed characters.
Esc
Closes the menu and returns focus to the trigger.
  • Icon-only triggers need an aria-label that names both the action and the object: Actions for Quarterly planning, not More.
  • Typeahead matches the item's text. When an item's content isn't plain text, set the label prop to what people would type.
  • Highlight vs. hover. Items highlight on pointer hover and keyboard focus alike (data-highlighted), so there is only ever one active item.

API reference

The root. Accepts every prop of the Base UI Menu.Root.

PropTypeDefault
open

Controlled open state.

booleanNo default
defaultOpen

Initial open state when uncontrolled.

booleanfalse
onOpenChange

Called when the menu opens or closes.

(open: boolean, details) => voidNo default
modal

Whether the rest of the page is inert while the menu is open.

booleantrue
loopFocus

Whether arrow-key focus wraps from the last item to the first.

booleantrue
disabled

Ignores user interaction with the trigger.

booleanfalse

The element that opens the menu. Use render to supply a Button.

PropTypeDefault
render

The element to render, usually <Button />.

ReactElement | (props, state) => ReactElementNo default
openOnHover

Also open the menu when the trigger is hovered.

booleanfalse
delay

Hover delay in ms before opening. Requires openOnHover.

number100
disabled

Disables the trigger.

booleanfalse

The popup, rendered in a portal and positioned against the trigger.

PropTypeDefault
side

Preferred side of the trigger. Flips automatically to stay in view.

"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"
align

Alignment against the trigger on the chosen side.

"start" | "center" | "end""start"
sideOffset

Gap between trigger and menu, in px.

number6
alignOffset

Shift along the alignment axis, in px.

number0
PropTypeDefault
variant

Destructive items use the danger color on text, icon and highlight.

"default" | "destructive""default"
inset

Adds leading space to align with items that have icons or indicators.

booleanfalse
onClick

Called when the item is activated by pointer or keyboard.

(event) => voidNo default
closeOnClick

Whether activating the item closes the menu.

booleantrue
disabled

Skips the item in keyboard navigation and ignores clicks.

booleanfalse
label

Text used for typeahead when the item's content isn't plain text.

stringNo default
PropTypeDefault
checked

Controlled checked state.

booleanNo default
defaultChecked

Initial checked state when uncontrolled.

booleanfalse
onCheckedChange

Called when the item is toggled.

(checked: boolean, details) => voidNo default
closeOnClick

Whether toggling closes the menu.

booleanfalse
inset

Kept for API symmetry; checkbox items always reserve indicator space.

booleanfalse
PropTypeDefault
value

Controlled selected value.

anyNo default
defaultValue

Initial selected value when uncontrolled.

anyNo default
onValueChange

Called when the selection changes.

(value: any, details) => voidNo default
disabled

Disables every item in the group.

booleanfalse
PropTypeDefault
valuerequired

The value this item selects.

anyNo default
closeOnClick

Whether selecting the item closes the menu.

booleanfalse
disabled

Skips the item and ignores selection.

booleanfalse

DropdownMenuSub wraps a nested menu. DropdownMenuSubTrigger accepts inset, disabled, openOnHover (default true), delay (default 100) and closeDelay (default 0). DropdownMenuSubContent accepts the same positioning props as DropdownMenuContent, defaulting to side="right", align="start", alignOffset={-5} and sideOffset={2}.

DropdownMenuLabel labels the surrounding DropdownMenuGroup and accepts inset. DropdownMenuSeparator draws a hairline between groups. DropdownMenuShortcut is a presentational <span> pushed to the item's trailing edge.