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

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

> A right-click (or long-press) menu opened at the pointer — the same item vocabulary as DropdownMenu, bound to a surface.

## When to use it

A ContextMenu attaches actions to a **surface or object** — a table row, a canvas node, a file tile — surfaced by the platform's contextual gesture (right-click, long-press). It's a power-user affordance: the same actions should always be reachable another way, because context menus are undiscoverable on their own.

It shares its entire part vocabulary with **[DropdownMenu](/overlay/dropdown-menu)** — the only difference is the trigger. Choose ContextMenu when the natural gesture is right-clicking the thing itself; choose DropdownMenu when there's a visible button (a "⋯" overflow) to click. Never make a context menu the *only* path to an action, and don't override the browser's native menu on plain text or links where users expect it.

## Props

### ContextMenu.Content

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

### ContextMenu.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

`ContextMenu.Trigger` is the surface; right-clicking (or long-pressing on touch) opens the menu at the pointer. Items support `startIcon`, `endHint`, and `tone="danger"` — same API as DropdownMenu.

```tsx
import { ContextMenu, Text } from "@protocore/pds";
import { Copy, Pin, Trash2 } from "lucide-react";

export default function ContextMenuBasics() {
  return (
    <ContextMenu.Root>
      <ContextMenu.Trigger asChild>
        <div
          style={{
            display: "grid",
            placeItems: "center",
            height: 120,
            border: "1px dashed var(--pds-border-ctrl)",
            color: "var(--pds-color-muted)",
            userSelect: "none",
          }}
        >
          <Text size="sm" color="secondary">
            Right-click this ledger row
          </Text>
        </div>
      </ContextMenu.Trigger>
      <ContextMenu.Content>
        <ContextMenu.Item startIcon={<Copy size={15} />} endHint="⌘C">
          Copy tx id
        </ContextMenu.Item>
        <ContextMenu.Item startIcon={<Pin size={15} />}>Pin to watchlist</ContextMenu.Item>
        <ContextMenu.Separator />
        <ContextMenu.Item tone="danger" startIcon={<Trash2 size={15} />}>
          Discard entry
        </ContextMenu.Item>
      </ContextMenu.Content>
    </ContextMenu.Root>
  );
}
```

### Toggles & submenus

The full menu vocabulary is available: `Label`, `CheckboxItem`, `Separator`, and a nested `Sub` / `SubTrigger` / `SubContent` with a `RadioGroup` inside.

```tsx
import { useState } from "react";
import { ContextMenu, Text } from "@protocore/pds";

export default function ContextMenuRich() {
  const [wrap, setWrap] = useState(true);
  const [theme, setTheme] = useState("mono");

  return (
    <ContextMenu.Root>
      <ContextMenu.Trigger asChild>
        <div
          style={{
            display: "grid",
            placeItems: "center",
            height: 120,
            border: "1px dashed var(--pds-border-ctrl)",
            userSelect: "none",
          }}
        >
          <Text size="sm" color="secondary">
            Right-click the log viewer
          </Text>
        </div>
      </ContextMenu.Trigger>
      <ContextMenu.Content>
        <ContextMenu.Label>View</ContextMenu.Label>
        <ContextMenu.CheckboxItem checked={wrap} onCheckedChange={setWrap}>
          Wrap long lines
        </ContextMenu.CheckboxItem>
        <ContextMenu.Separator />
        <ContextMenu.Sub>
          <ContextMenu.SubTrigger>Syntax theme</ContextMenu.SubTrigger>
          <ContextMenu.SubContent>
            <ContextMenu.RadioGroup value={theme} onValueChange={setTheme}>
              <ContextMenu.RadioItem value="mono">Mono</ContextMenu.RadioItem>
              <ContextMenu.RadioItem value="solar">Solarized</ContextMenu.RadioItem>
              <ContextMenu.RadioItem value="dracula">Dracula</ContextMenu.RadioItem>
            </ContextMenu.RadioGroup>
          </ContextMenu.SubContent>
        </ContextMenu.Sub>
      </ContextMenu.Content>
    </ContextMenu.Root>
  );
}
```

## Do & don't

**Do**

- Attach it to a concrete object — a row, a node, a tile.
- Mirror every context action in a visible control too (an overflow menu).
- Reuse the DropdownMenu item vocabulary for a consistent feel.
- Keep the list short; context menus are for the object's top actions.

**Don't**

- Make a context menu the only way to reach an action.
- Override the native menu on ordinary selectable text or links.
- Pack unrelated global commands in — those belong in a menu or command palette.
- Nest deep submenu trees that are hard to reach at the pointer.

## Accessibility

**Keyboard**

| Keys | Action |
| --- | --- |
| `Shift + F10 / Menu key` | Opens the context menu from the keyboard on the focused trigger. |
| `↑ / ↓` | Moves focus between items (roving tabindex). |
| `→ / ←` | Opens / closes a submenu. |
| `Home / End` | Jumps to the first / last item. |
| `A–Z` | Typeahead to the next matching item. |
| `Esc` | Closes the menu and returns focus to the trigger. |

**Notes**

- Opens on contextmenu (right-click), long-press on touch, and Shift+F10 / the Menu key.
- Same menu roles as DropdownMenu; focus is trapped while open and restored on close.
- Because it's gesture-triggered and undiscoverable, always provide an equivalent visible control.
- Checkbox and radio items announce their state to assistive tech.

## Related

`dropdown-menu`, `popover`, `data-table`, `listing-row`

---

© 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/
