Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Dropdown Menu

A menu of actions triggered by a button — items, submenus, separators, labels, shortcuts, and checkbox/radio selections.

Status
stable
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-menu

The 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
DropdownMenuCheckboxItem
DropdownMenuContent
DropdownMenuGroup
DropdownMenuItem
DropdownMenuLabel
DropdownMenuRadioGroup
DropdownMenuRadioItem
DropdownMenuSeparator
DropdownMenuShortcut
DropdownMenuSub
DropdownMenuSubContent
DropdownMenuSubTrigger
DropdownMenuTrigger — data-slot="dropdown-menu-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"). Use render to compose with a Button.
  • DropdownMenuContent — the floating popup; portals to <body> and positions against the trigger (data-slot="dropdown-menu-content"). Accepts side, align, sideOffset, collisionPadding, exposes portalProps / positionerProps, and can wrap items in an optional Base UI Viewport via viewportProps.
  • DropdownMenuItem — a selectable action (data-slot="dropdown-menu-item"). Supports tone="destructive" and inset.
  • 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>

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 adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

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

AttributeValues
data-slot"dropdown-menu-trigger"

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 adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

DropdownMenuRadioGroup adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

PropTypeDefaultDescription
alignAlign'start'Alignment relative to the anchor along the chosen side.
collisionPaddingPadding8Padding from the collision boundary so the popup never touches the viewport edge.
portalPropsOmit<MenuPortalProps, "children">Props forwarded to the underlying Base UI Portal.
positionerPropsOmit<MenuPositionerProps, "align" | "children" | "collisionPadding" | "side" | "sideOffset">Props forwarded to the underlying Base UI Positioner.
sideSide'bottom'Which side of the anchor to render against. May flip to avoid collisions.
sideOffsetnumber | OffsetFunction4Distance in pixels between the anchor and the popup.
viewportPropsOmit<MenuViewportProps, "children">Props forwarded to an optional Base UI Viewport that wraps popup children.
PropTypeDefaultDescription
insetbooleanfalseAdds inline-start padding so the label aligns with rows that carry a leading indicator.
toneMenuItemTone"default"destructive tints the row for delete/remove actions.

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 adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

PropTypeDefaultDescription
insetbooleanfalseIndents the label to line up with inset rows.

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 adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).

PropTypeDefaultDescription
insetbooleanfalseIndents the trigger to line up with inset rows.
PropTypeDefaultDescription
alignAlign'start'Alignment relative to the anchor along the chosen side.
collisionPaddingPadding8Padding from the collision boundary so the popup never touches the viewport edge.
portalPropsOmit<MenuPortalProps, "children">Props forwarded to the underlying Base UI Portal.
positionerPropsOmit<MenuPositionerProps, "align" | "children" | "collisionPadding" | "side" | "sideOffset">Props forwarded to the underlying Base UI Positioner.
sideSide'bottom'Which side of the anchor to render against. May flip to avoid collisions.
sideOffsetnumber | OffsetFunction4Distance in pixels between the anchor and the popup.
viewportPropsOmit<MenuViewportProps, "children">Props forwarded to an optional Base UI Viewport that wraps popup children.

Accessibility

  • The popup renders with role="menu"; items are menuitem, menuitemcheckbox, and menuitemradio. A DropdownMenuLabel inside 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 no outline: none without 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.
KeyAction
Enter / Space / Open the menu from the trigger.
/ Move between items (wraps at the ends).
Open the focused submenu.
/ EscClose the current submenu (or the whole menu).
Enter / SpaceActivate the focused item.
AZTypeahead — jump to the next item matching the typed letters.
ContractStates tested
Behaviourdefault, active, checked, disabled, error, open, selected
Accessibilitylabeled, semantic-html
Visualdefault, checked, error

Do / Don't

Do
Group related actions with a label and separate destructive actions with a separator.
Don't
Use a dropdown menu for selecting a single value from a form — use a Select instead.

On this page