<!-- Protocore Design System — DropdownMenu -->
# DropdownMenu

- **Category:** Overlay (`overlay`)
- **Slug:** `overlay/dropdown-menu`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { DropdownMenu } from "@protocore/pds";`
- **Docs:** https://pds.protocore.io/components/overlay/dropdown-menu

> A menu of actions opened from a button — items, checkboxes, radio groups, and nested submenus with full keyboard navigation.

## When to use it

A DropdownMenu presents a **list of commands or a set of toggles** launched from a button — a row's overflow menu, a "⋯ Actions" control, a view/filter switcher. Items are actions (they do something on select), and the menu closes after a choice.

Don't confuse it with a **[Select](/inputs/select)**: a Select edits a single form value and shows the current selection in its trigger; a DropdownMenu fires actions. For the same menu opened by **right-click on a surface** rather than a button, use **[ContextMenu](/overlay/context-menu)** — identical parts, different trigger. If you need arbitrary interactive content (a form, a slider) rather than a list of items, that's a **[Popover](/overlay/popover)**.

## Props

### DropdownMenu.Content

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `className` | `string` | no | — | — |
| `style` | `CSSProperties` | no | — | — |

### DropdownMenu.Item

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `asChild` | `boolean` | no | — | — |
| `className` | `string` | no | — | — |
| `endHint` | `ReactNode` | no | — | Optional trailing hint (shortcut, chevron). |
| `startIcon` | `ReactNode` | no | — | Optional leading icon slot. |
| `style` | `CSSProperties` | no | — | — |
| `tone` | `enum` | no | `neutral` | Set `"danger"` to render a destructive item (danger-fg text). |

## Examples

### Basics

`DropdownMenu.Item` takes a `startIcon`, an `endHint` (for a shortcut), and `tone="danger"` for destructive actions. `DropdownMenu.Separator` rules off a group.

```tsx
import { DropdownMenu, Button } from "@protocore/pds";
import { Copy, Download, Share2, Trash2 } from "lucide-react";

export default function DropdownMenuBasics() {
  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <Button variant="secondary">Actions</Button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Content>
        <DropdownMenu.Item startIcon={<Copy size={15} />} endHint="⌘C">
          Copy hash
        </DropdownMenu.Item>
        <DropdownMenu.Item startIcon={<Download size={15} />}>Export logs</DropdownMenu.Item>
        <DropdownMenu.Item startIcon={<Share2 size={15} />}>Share snapshot</DropdownMenu.Item>
        <DropdownMenu.Separator />
        <DropdownMenu.Item tone="danger" startIcon={<Trash2 size={15} />}>
          Delete node
        </DropdownMenu.Item>
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
}
```

### Checkbox & radio items

`CheckboxItem` toggles independently (`checked` / `onCheckedChange`); `RadioItem` inside a `RadioGroup` is single-select (`value` / `onValueChange`). `DropdownMenu.Label` heads each section.

```tsx
import { useState } from "react";
import { DropdownMenu, Button } from "@protocore/pds";

export default function DropdownMenuCheckboxRadio() {
  const [showResolved, setShowResolved] = useState(true);
  const [showMuted, setShowMuted] = useState(false);
  const [env, setEnv] = useState("prod");

  return (
    <DropdownMenu.Root>
      <DropdownMenu.Trigger asChild>
        <Button variant="secondary">View</Button>
      </DropdownMenu.Trigger>
      <DropdownMenu.Content>
        <DropdownMenu.Label>Columns</DropdownMenu.Label>
        <DropdownMenu.CheckboxItem checked={showResolved} onCheckedChange={setShowResolved}>
          Resolved
        </DropdownMenu.CheckboxItem>
        <DropdownMenu.CheckboxItem checked={showMuted} onCheckedChange={setShowMuted}>
          Muted
        </DropdownMenu.CheckboxItem>
        <DropdownMenu.Separator />
        <DropdownMenu.Label>Environment</DropdownMenu.Label>
        <DropdownMenu.RadioGroup value={env} onValueChange={setEnv}>
          <DropdownMenu.RadioItem value="prod">Production</DropdownMenu.RadioItem>
          <DropdownMenu.RadioItem value="staging">Staging</DropdownMenu.RadioItem>
          <DropdownMenu.RadioItem value="dev">Dev</DropdownMenu.RadioItem>
        </DropdownMenu.RadioGroup>
      </DropdownMenu.Content>
    </DropdownMenu.Root>
  );
}
```

## Do & don't

**Do**

- Use it for action lists and quick toggles launched from a button.
- Group related items with DropdownMenu.Label and Separator.
- Put shortcut hints in endHint so they align on the right.
- Mark destructive items with tone="danger" and place them last.

**Don't**

- Use it to set a form value — that's a Select.
- Nest more than one level of submenu; deep trees are hard to navigate.
- Fill items with arbitrary form controls — reach for a Popover.
- Leave a danger item mid-list where it's easy to hit by accident.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Enter / Space` | Opens the menu from the trigger; activates the focused item. |
| `↑ / ↓` | Moves focus between items (roving tabindex). |
| `→` | Opens a submenu from its SubTrigger. |
| `←` | Closes the current submenu, returning to its trigger. |
| `Home / End` | Jumps to the first / last item. |
| `A–Z` | Typeahead — jumps to the next item starting with the typed character. |
| `Esc` | Closes the menu and returns focus to the trigger. |

**Notes**

- Roles from Radix: menu / menuitem / menuitemcheckbox / menuitemradio, correctly wired.
- Focus is trapped in the open menu and restored to the trigger on close.
- Checkbox and radio items expose their checked state to assistive tech.
- Icons in items are decorative; the label carries the accessible name.

## Related

`context-menu`, `popover`, `select`, `button`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
