Dropdown Menu

Context menu component built on Radix DropdownMenu. Supports groups, submenus, checkbox/radio items, and keyboard shortcuts.

The Dropdown Menu component displays a list of actions in a floating surface anchored to a trigger. It is built on Radix UI DropdownMenu and styled with PyColors tokens (bg-popover, border-border, text-popover-foreground).

Use Dropdown Menu for:

  • Row actions (••• menus)
  • Editor/tool menus
  • Compact command lists (rename, duplicate, delete)
  • Option selectors (checkbox/radio groups)

Import

import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuCheckboxItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuGroup,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
} from "@/components/ui/dropdown-menu";

Basic usage

<DropdownMenu>
  <DropdownMenuTrigger asChild>
    <Button variant="outline" size="sm">Open menu</Button>
  </DropdownMenuTrigger>

  <DropdownMenuContent>
    <DropdownMenuItem>Profile</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";

export function BasicDropdownMenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">
          Open menu
        </Button>
      </DropdownMenuTrigger>

      <DropdownMenuContent align="start">
        <DropdownMenuItem>Profile</DropdownMenuItem>
        <DropdownMenuItem>Settings</DropdownMenuItem>
        <DropdownMenuSeparator />
        <DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Grouping, labels, and shortcuts

Use DropdownMenuLabel to title a section, DropdownMenuGroup to group items, and DropdownMenuShortcut to render right-aligned hints.

<DropdownMenuContent>
  <DropdownMenuLabel>Project</DropdownMenuLabel>

  <DropdownMenuGroup>
    <DropdownMenuItem>
      Rename <DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
    </DropdownMenuItem>
  </DropdownMenuGroup>

  <DropdownMenuSeparator />

  <DropdownMenuItem className="text-destructive">
    Delete <DropdownMenuShortcut>⌫</DropdownMenuShortcut>
  </DropdownMenuItem>
</DropdownMenuContent>
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";

export function GroupedMenu() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">Actions</Button>
      </DropdownMenuTrigger>

      <DropdownMenuContent align="start">
        <DropdownMenuLabel>Project</DropdownMenuLabel>

        <DropdownMenuGroup>
          <DropdownMenuItem>
            Rename <DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            Duplicate <DropdownMenuShortcut>⌘D</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>

        <DropdownMenuSeparator />

        <DropdownMenuItem className="text-destructive">
          Delete <DropdownMenuShortcut>⌫</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Checkbox items

Interactive checkbox examples must live in a Client Component (hooks + event handlers). This page renders them via CheckboxMenuPreview.

<DropdownMenuCheckboxItem checked={enabled} onCheckedChange={setEnabled}>
  Enabled
</DropdownMenuCheckboxItem>
"use client";

import * as React from "react";
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuCheckboxItem,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";

export function CheckboxMenuPreview() {
  const [enabled, setEnabled] = React.useState(true);
  const [pinned, setPinned] = React.useState(false);

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">Options</Button>
      </DropdownMenuTrigger>

      <DropdownMenuContent align="start">
        <DropdownMenuCheckboxItem
          checked={enabled}
          onCheckedChange={(v) => setEnabled(Boolean(v))}
        >
          Enabled
        </DropdownMenuCheckboxItem>

        <DropdownMenuCheckboxItem
          checked={pinned}
          onCheckedChange={(v) => setPinned(Boolean(v))}
        >
          Pinned
        </DropdownMenuCheckboxItem>

        <DropdownMenuSeparator />

        <DropdownMenuCheckboxItem checked disabled>
          Disabled option
        </DropdownMenuCheckboxItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Radio group

Same rule: hooks go into a client component (RadioMenuPreview).

<DropdownMenuRadioGroup value={value} onValueChange={setValue}>
  <DropdownMenuRadioItem value="system">System</DropdownMenuRadioItem>
  <DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
"use client";

import * as React from "react";
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";

export function RadioMenuPreview() {
  const [theme, setTheme] = React.useState("system");

  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">Theme</Button>
      </DropdownMenuTrigger>

      <DropdownMenuContent align="start">
        <DropdownMenuLabel>Appearance</DropdownMenuLabel>

        <DropdownMenuRadioGroup value={theme} onValueChange={setTheme}>
          <DropdownMenuRadioItem value="system">System</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="light">Light</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="dark">Dark</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>

        <DropdownMenuSeparator />

        <DropdownMenuRadioItem value="contrast" disabled>
          High contrast (soon)
        </DropdownMenuRadioItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

Use DropdownMenuSub, DropdownMenuSubTrigger, and DropdownMenuSubContent to nest options.

<DropdownMenuSub>
  <DropdownMenuSubTrigger>Export</DropdownMenuSubTrigger>
  <DropdownMenuSubContent>
    <DropdownMenuItem>PDF</DropdownMenuItem>
    <DropdownMenuItem>CSV</DropdownMenuItem>
  </DropdownMenuSubContent>
</DropdownMenuSub>
import {
  DropdownMenu,
  DropdownMenuTrigger,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSub,
  DropdownMenuSubTrigger,
  DropdownMenuSubContent,
  DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu";
import { Button } from "@/components/ui/button";

export function SubmenuExample() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger asChild>
        <Button variant="outline" size="sm">More</Button>
      </DropdownMenuTrigger>

      <DropdownMenuContent align="start">
        <DropdownMenuItem>Rename</DropdownMenuItem>

        <DropdownMenuSub>
          <DropdownMenuSubTrigger>Export</DropdownMenuSubTrigger>
          <DropdownMenuSubContent>
            <DropdownMenuItem>PDF</DropdownMenuItem>
            <DropdownMenuItem>CSV</DropdownMenuItem>
          </DropdownMenuSubContent>
        </DropdownMenuSub>

        <DropdownMenuSeparator />

        <DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  );
}

API

Components

ComponentDescription
DropdownMenuRoot container
DropdownMenuTriggerAnchor element (use asChild for custom triggers)
DropdownMenuContentFloating surface (supports Radix positioning props)
DropdownMenuItemStandard action item
DropdownMenuCheckboxItemToggle item (checked/unchecked)
DropdownMenuRadioGroupExclusive selection group
DropdownMenuRadioItemItem within a radio group
DropdownMenuLabelSection label
DropdownMenuSeparatorDivider
DropdownMenuShortcutRight-aligned hint (visual only)
DropdownMenuGroupGroup wrapper
DropdownMenuSubSubmenu root
DropdownMenuSubTriggerSubmenu trigger item
DropdownMenuSubContentSubmenu surface

Wrapper additions

  • DropdownMenuItem supports { inset?: boolean }
  • DropdownMenuLabel supports { inset?: boolean }
  • DropdownMenuSubTrigger supports { inset?: boolean }

Accessibility

  • Radix provides keyboard navigation by default:
    • Enter/Space activates an item
    • Arrow keys navigate
    • Esc closes the menu
  • Prefer DropdownMenuTrigger asChild to preserve correct semantics (e.g. a real <button>).
  • Avoid placing complex interactive controls (inputs, comboboxes) inside dropdown menus unless necessary.
  • For destructive actions, consider a confirmation dialog.

Design guidelines

  • Keep items short and action-oriented (verb-first labels).
  • Use separators to split groups; don’t over-separate.
  • Use inset for hierarchical menus or when aligning with item indicators.
  • Use submenus sparingly (depth 1 is usually enough).