Wuko
Components

DropdownMenu

Displays a menu of actions or options triggered by a button. Supports items, labels, separators, keyboard shortcuts, checkbox/radio items, nested submenus, and a destructive variant. Built on Radix UI for full keyboard navigation and accessibility.

Installation

terminal
npx shadcn@latest add @wuko/dropdown-menu

Usage

components/example.tsx
import { Button } from "@/components/ui/button";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Open</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuItem>Profile</DropdownMenuItem>
        <DropdownMenuItem>Billing</DropdownMenuItem>
        <DropdownMenuItem>Settings</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Composition

Use the following composition to build a DropdownMenu.

text
DropdownMenu
├── DropdownMenuTrigger
└── DropdownMenuContent
    ├── DropdownMenuGroup
    │   ├── DropdownMenuLabel
    │   ├── DropdownMenuItem
    │   └── DropdownMenuItem
    ├── DropdownMenuSeparator
    ├── DropdownMenuGroup
    │   ├── DropdownMenuCheckboxItem
    │   └── DropdownMenuCheckboxItem
    ├── DropdownMenuSeparator
    ├── DropdownMenuRadioGroup
    │   ├── DropdownMenuRadioItem
    │   └── DropdownMenuRadioItem
    └── DropdownMenuSub
        ├── DropdownMenuSubTrigger
        └── DropdownMenuSubContent
            ├── DropdownMenuItem
            └── DropdownMenuItem

Basic

A basic dropdown menu with a label and separator.

tsx
<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">Open</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuLabel>My Account</DropdownMenuLabel>
    <DropdownMenuSeparator />
    <DropdownMenuItem>Profile</DropdownMenuItem>
    <DropdownMenuItem>Billing</DropdownMenuItem>
    <DropdownMenuItem>Settings</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Use DropdownMenuSub to nest secondary actions.

tsx
<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">More options</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>New file</DropdownMenuItem>
    <DropdownMenuItem>Open</DropdownMenuItem>
    <DropdownMenuSub>
      <DropdownMenuSubTrigger>Share</DropdownMenuSubTrigger>
      <DropdownMenuSubContent>
        <DropdownMenuItem>Email link</DropdownMenuItem>
        <DropdownMenuItem>Copy link</DropdownMenuItem>
        <DropdownMenuItem>Embed</DropdownMenuItem>
      </DropdownMenuSubContent>
    </DropdownMenuSub>
  </DropdownMenuContent>
</DropdownMenu>

Shortcuts

Add DropdownMenuShortcut to show keyboard hints right-aligned in each item.

tsx
<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">Edit</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>
      Copy
      <DropdownMenuShortcut>⌘C</DropdownMenuShortcut>
    </DropdownMenuItem>
    <DropdownMenuItem>
      Paste
      <DropdownMenuShortcut>⌘V</DropdownMenuShortcut>
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Icons

Combine icons with labels for quicker visual scanning.

tsx
import { User, Settings, LogOut } from "lucide-react";

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">Options</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>
      <User className="size-4" />
      Profile
    </DropdownMenuItem>
    <DropdownMenuItem>
      <Settings className="size-4" />
      Settings
    </DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem>
      <LogOut className="size-4" />
      Sign out
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Checkboxes

Use DropdownMenuCheckboxItem for independent toggle items. Each item maintains its own checked state.

tsx
"use client";

import * as React from "react";

export function Example() {
  const [showBookmarks, setShowBookmarks] = React.useState(true);
  const [showFullURLs, setShowFullURLs] = React.useState(false);

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">View</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>Appearance</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuCheckboxItem
          checked={showBookmarks}
          onCheckedChange={setShowBookmarks}
        >
          Show Bookmarks
        </DropdownMenuCheckboxItem>
        <DropdownMenuCheckboxItem
          checked={showFullURLs}
          onCheckedChange={setShowFullURLs}
        >
          Show Full URLs
        </DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Radio Group

Use DropdownMenuRadioGroup for mutually exclusive choices — pick one and only one.

tsx
"use client";

import * as React from "react";

export function Example() {
  const [position, setPosition] = React.useState("bottom");

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Panel position</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>Position</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
          <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Destructive

Use variant="destructive" for irreversible actions like delete. Renders the item with the danger color.

tsx
import { Trash2 } from "lucide-react";

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline">Actions</Button>
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>View</DropdownMenuItem>
    <DropdownMenuItem>Duplicate</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive">
      <Trash2 className="size-4" />
      Delete
    </DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>

Complex

A richer example combining groups, labels, icons, shortcuts, and a destructive item.

tsx
"use client";

import * as React from "react";
import { User, Settings, CreditCard, LogOut } from "lucide-react";

export function Example() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline">Account</Button>
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>My Account</DropdownMenuLabel>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuItem>
            <User className="size-4" />
            Profile
            <DropdownMenuShortcut>⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <CreditCard className="size-4" />
            Billing
            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <Settings className="size-4" />
            Settings
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive">
          <LogOut className="size-4" />
          Sign out
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Sub-components

Each sub-component forwards className and all native attributes for its underlying Radix primitive. The most useful consumer-facing props are listed below.

PropTypeDefaultDescription
DropdownMenuRadix RootThe root container. Forwards all props to Radix's DropdownMenu.Root.
DropdownMenuTriggerRadix TriggerThe button or element that opens the menu. Supports asChild to compose with a Button.
DropdownMenuContentRadix ContentThe floating panel. Renders into a portal at the end of body. Supports align, side, sideOffset props from Radix.
DropdownMenuItemRadix ItemA clickable item. Accepts variant="default" | "destructive" for destructive actions, and inset for icon alignment.
DropdownMenuCheckboxItemRadix CheckboxItemA toggleable item. Controlled via checked and onCheckedChange.
DropdownMenuRadioItemRadix RadioItemA single choice in a DropdownMenuRadioGroup. Pass value; the group's value selects one.
DropdownMenuRadioGroupRadix RadioGroupWraps multiple DropdownMenuRadioItems. Controlled via value and onValueChange.
DropdownMenuLabelRadix LabelSection heading inside the menu. Not selectable. Supports inset for icon alignment.
DropdownMenuSeparatorRadix SeparatorA thin horizontal divider between sections.
DropdownMenuShortcutspanDisplays a keyboard shortcut hint right-aligned in an item (e.g., ⌘C).
DropdownMenuGroupRadix GroupWraps related items so screen readers announce them as a group.
DropdownMenuSubRadix SubWraps a nested submenu (DropdownMenuSubTrigger + Content).
DropdownMenuSubTriggerRadix SubTriggerThe item that opens a submenu. Automatically renders a chevron icon on the right.
DropdownMenuSubContentRadix SubContentThe floating panel for a submenu.

Accessibility

  • Built on Radix UI primitives. Full keyboard support: Enter or Space opens the menu, Arrow keys move between items, Esc closes, Tab moves focus out of the menu.
  • The trigger and content are linked via aria-controls and aria-expanded automatically. Screen readers announce the menu state and item count.
  • Use DropdownMenuLabel as a section heading. Labels are not focusable, but screen readers announce them when entering a group.
  • For icon-only triggers, set aria-label on the trigger Button so the menu's purpose is announced.
  • Checkbox and radio items expose their checked state via aria-checked; the indicator is aria-hidden so the announcement comes from the item itself.