Dropdown Menu
A menu of actions triggered by a button — items, submenus, separators, labels, shortcuts, and checkbox/radio selections.
- Status
- Since
0.1.0- Accessibility pattern
- APG menu button
Last updated
Install
Add Dropdown Menu from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/dropdown-menuThe same command installs the registry items it composes: @vegastack/floating-surface.
Usage
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";
<DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline">Open menu</Button>} />
<DropdownMenuContent>
<DropdownMenuItem>Profile</DropdownMenuItem>
<DropdownMenuItem tone="destructive">Delete account</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>;Anatomy
DropdownMenu is a compound component built on Base UI's Menu. The trigger and the content
sit inside the root; the content portals to <body> and positions itself against the trigger.
<DropdownMenu>
<DropdownMenuTrigger />
<DropdownMenuContent>
<DropdownMenuGroup>
<DropdownMenuLabel />
<DropdownMenuItem />
<DropdownMenuShortcut />
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem />
<DropdownMenuRadioGroup>
<DropdownMenuRadioItem />
</DropdownMenuRadioGroup>
<DropdownMenuSub>
<DropdownMenuSubTrigger />
<DropdownMenuSubContent>
<DropdownMenuItem />
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>DropdownMenu— the root that groups every part (Menu.Root; renders no element).DropdownMenuTrigger— the button that opens the menu (data-slot="dropdown-menu-trigger"). Userenderto compose with aButton.DropdownMenuContent— the floating popup; portals to<body>and positions against the trigger (data-slot="dropdown-menu-content"). Acceptsside,align,sideOffset,collisionPadding, exposesportalProps/positionerProps, and can wrap items in an optional Base UI Viewport viaviewportProps.DropdownMenuItem— a selectable action (data-slot="dropdown-menu-item"). Supportstone="destructive"andinset.DropdownMenuGroup+DropdownMenuLabel— a labelled section; the label becomes the group's accessible name.DropdownMenuSeparator— a divider between sections (role="separator").DropdownMenuShortcut— an inline-end-aligned keyboard hint inside an item.DropdownMenuCheckboxItem— a togglable item with a check indicator (checked/onCheckedChange).DropdownMenuRadioGroup+DropdownMenuRadioItem— single-select items (value/onValueChange).DropdownMenuSub+DropdownMenuSubTrigger+DropdownMenuSubContent— a nested submenu.
Examples
A rich menu with grouped items, labels, keyboard shortcuts, checkbox + radio selections, a disabled item, a destructive action, and a nested submenu.
Items
Items take leading lucide-react icons as children, a trailing DropdownMenuShortcut, and a
tone="destructive" for delete/remove actions. Use inset to align items that have no leading
icon with those that do.
<DropdownMenuItem>
<Settings />
Settings
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem tone="destructive">
<Trash2 />
Delete
</DropdownMenuItem>Inset alignment
When a menu mixes items that have a leading icon (or a checkbox/radio indicator) with items
that don't, add inset to the icon-less items — and to a DropdownMenuLabel — so their text
lines up with the indented rows instead of jumping back to the edge.
<DropdownMenuGroup>
<DropdownMenuLabel inset>Appearance</DropdownMenuLabel>
<DropdownMenuItem>
<User />
Profile
</DropdownMenuItem>
<DropdownMenuItem inset>Compact mode</DropdownMenuItem>
</DropdownMenuGroup>Checkbox & radio items
DropdownMenuCheckboxItem toggles a setting and stays open on click; DropdownMenuRadioGroup
wraps DropdownMenuRadioItems for single-select.
<DropdownMenuCheckboxItem checked={showGrid} onCheckedChange={setShowGrid}>
Show grid
</DropdownMenuCheckboxItem>
<DropdownMenuRadioGroup value={role} onValueChange={setRole}>
<DropdownMenuRadioItem value="admin">Admin</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="member">Member</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>Submenus
Nest a DropdownMenuSub to reveal a second-level popup. The trigger shows a trailing chevron and
opens on hover or with the → arrow key.
<DropdownMenuSub>
<DropdownMenuSubTrigger>Invite users</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>Email</DropdownMenuItem>
<DropdownMenuItem>Message</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>API Reference
DropdownMenu
DropdownMenu adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuTrigger
DropdownMenuTrigger adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on DropdownMenuTrigger
| Attribute | Values |
|---|---|
data-slot | "dropdown-menu-trigger" |
DropdownMenuGroup
DropdownMenuGroup adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuSub
DropdownMenuSub adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuRadioGroup
DropdownMenuRadioGroup adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | 'start' | Alignment relative to the anchor along the chosen side. |
collisionPadding | Padding | 8 | Padding from the collision boundary so the popup never touches the viewport edge. |
portalProps | Omit<MenuPortalProps, "children"> | — | Props forwarded to the underlying Base UI Portal. |
positionerProps | Omit<MenuPositionerProps, "align" | "children" | "collisionPadding" | "side" | "sideOffset"> | — | Props forwarded to the underlying Base UI Positioner. |
side | Side | 'bottom' | Which side of the anchor to render against. May flip to avoid collisions. |
sideOffset | number | OffsetFunction | 4 | Distance in pixels between the anchor and the popup. |
viewportProps | Omit<MenuViewportProps, "children"> | — | Props forwarded to an optional Base UI Viewport that wraps popup children. |
DropdownMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Adds inline-start padding so the label aligns with rows that carry a leading indicator. |
tone | MenuItemTone | "default" | destructive tints the row for delete/remove actions. |
DropdownMenuCheckboxItem
DropdownMenuCheckboxItem adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuRadioItem
DropdownMenuRadioItem adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuLabel
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Indents the label to line up with inset rows. |
DropdownMenuSeparator
DropdownMenuSeparator adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuShortcut
DropdownMenuShortcut adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
DropdownMenuSubTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Indents the trigger to line up with inset rows. |
DropdownMenuSubContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | 'start' | Alignment relative to the anchor along the chosen side. |
collisionPadding | Padding | 8 | Padding from the collision boundary so the popup never touches the viewport edge. |
portalProps | Omit<MenuPortalProps, "children"> | — | Props forwarded to the underlying Base UI Portal. |
positionerProps | Omit<MenuPositionerProps, "align" | "children" | "collisionPadding" | "side" | "sideOffset"> | — | Props forwarded to the underlying Base UI Positioner. |
side | Side | 'bottom' | Which side of the anchor to render against. May flip to avoid collisions. |
sideOffset | number | OffsetFunction | 4 | Distance in pixels between the anchor and the popup. |
viewportProps | Omit<MenuViewportProps, "children"> | — | Props forwarded to an optional Base UI Viewport that wraps popup children. |
Accessibility
- The popup renders with
role="menu"; items aremenuitem,menuitemcheckbox, andmenuitemradio. ADropdownMenuLabelinside a group becomes that group's accessible name. - Full keyboard support via Base UI's roving focus; the highlighted item uses
data-highlighted(bg-accent text-accent-foreground) — there is nooutline: nonewithout a visible focus state. - The menu is modal by default: page scroll locks and focus is trapped while open, then returns to the trigger on close.
- Icons inside items are decorative; the item's text carries the meaning.
| Key | Action |
|---|---|
| Enter / Space / ↓ | Open the menu from the trigger. |
| ↑ / ↓ | Move between items (wraps at the ends). |
| → | Open the focused submenu. |
| ← / Esc | Close the current submenu (or the whole menu). |
| Enter / Space | Activate the focused item. |
| A–Z | Typeahead — jump to the next item matching the typed letters. |
| Contract | States tested |
|---|---|
| Behaviour | default, active, checked, disabled, error, open, selected |
| Accessibility | labeled, semantic-html |
| Visual | default, checked, error |
Do / Don't
Tooltip
A floating label that appears on hover or focus — smart shared delay, rich content, optional keyboard hints, and collision-aware positioning.
Context Menu
A menu of actions revealed by right-clicking (or long-pressing) a target — items, submenus, separators, labels, shortcuts, and checkbox/radio selections.