# ContextMenu

The menu a right-click opens.

- Group: Overlays
- Import: `import { ContextMenu } from '@misoto22/folio'`
- Page: https://ui.misoto22.com/components/context-menu/
- Related: dropdown-menu

## When to reach for it

Never as the only way to reach an action. Touch users, trackpad users and keyboard users may have no way to open it.

## Anatomy

- **Trigger** (required) — The region a secondary click opens the menu over. Radix wraps children in its own inline span unless you pass asChild, and sets -webkit-touch-callout: none on it so the OS text callout does not fire first.
- **Panel** (required) — The portalled menu, placed at the POINTER rather than against the trigger — there is no side or align to set here, only the 8px collision padding that keeps it inside the viewport or the OverlayContainer frame.
- **Item** — A row, taking the same icon, destructive and disabled props as DropdownMenuItem — icon in either spelling — and highlighting on the same data-highlighted.
- **Label** — A mono eyebrow, on its own, and visual only. ContextMenuGroup is the one that heads rows: it renders role="group" and points its aria-labelledby at this.
- **Group** — A named section: role="group" around the rows, with label rendered inside it as the Label and named through aria-labelledby.
- **Separator** — A hairline between groups, as a real role="separator".

## Best practices

### Do

- Pass asChild and hand it the element itself: without it Radix inserts a span between you and your child, and that span becomes the flex or grid item while your card is laid out inside it as inline content.
- Build the same array of actions into a DropdownMenu behind an overflow button — the two take identical icon, destructive and disabled props, so one list feeds both and the right-click becomes the shortcut rather than the only door.
- Wrap the subtree in OverlayContainer when the right-clickable region lives in a scrolling or bounded frame: this is the one panel whose position the reader personally chose, and a flip against a viewport edge they cannot see lands it somewhere they did not point.

### Don’t

- Do not read “no touch support” as the whole story: Radix opens the menu on a 700ms long press for touch and pen, but cancels the moment the pointer moves — so on a scrollable list the long press and the scroll gesture compete and the scroll usually wins.
- A bare ContextMenuLabel over rows is a picture of a heading: Radix’s MenuLabel is a plain div with no role and nothing tying it to what follows, so the sections a sighted reader sees arrive as one undivided list. ContextMenuGroup renders both halves and wires them together.
- Radix defaults modal to true here too, so the page behind is scroll-locked and pointer-inert while the menu is open — a right-click menu over a long list stops the list moving under it, which is right for a short action list and wrong for anything the reader must scroll to answer.

## Keyboard

- Shift / F10 — Opens the menu from the keyboard, where the platform supports it.
- ↑ / ↓ — Moves between items.
- Escape — Closes it.

## ContextMenu

Re-export of `ContextMenuPrimitive.Root`.

Radix ContextMenu root and trigger, as typed passthroughs.

## ContextMenuTrigger

Re-export of `ContextMenuPrimitive.Trigger`.

## ContextMenuContent

The menu a right-click opens. Never the only way to reach an action. A context menu is opened by a secondary pointer button or a keyboard's own menu key, and a meaningful share of readers have neither — a touch user, someone on a trackpad they have not configured, anyone driving the page by keyboard alone. Whatever is in here belongs somewhere reachable too: a row's overflow button, a toolbar.

## ContextMenuItem

A row. Highlight follows `data-highlighted`, which covers hover and keyboard.

### Props

- `icon` — `RemixiconComponentType | ReactNode`. Optional leading icon. Either spelling — `icon={Copy}` passes the component and this sizes it, `icon={<Copy size={16} />}` passes the element and this places it.
- `destructive` — `boolean` default `false`. Paints the row as destructive. Use for delete, revoke, disconnect.

Also accepts: `ComponentProps<typeof ContextMenuPrimitive.Item>`.

## ContextMenuSeparator

Hairline divider between groups.

## ContextMenuLabel

Mono eyebrow heading, on its own. Visual only: Radix renders it as a bare `<div>` with no role. Reach for `ContextMenuGroup` when the eyebrow is a HEADING over rows; this is right for a line that heads nothing.

## ContextMenuGroup

A named section of a menu. The eyebrow alone was a picture of a heading: Radix's `MenuLabel` carries no role and no `aria-labelledby` wiring, so a sighted reader saw sections and a screen-reader user got one undifferentiated list. This renders the group, renders the label inside it, and points the one at the other.

### Props

- `label` — `ReactNode`. The eyebrow over the rows, and the group's accessible name.

Also accepts: `ComponentProps<typeof ContextMenuPrimitive.Group>`.

## Example — on a region

```tsx
<ContextMenu>
  <ContextMenuTrigger asChild>
    <div className="grid h-28 w-full max-w-sm place-items-center rounded-(--radius) border border-dashed border-(--rule-2) text-sm text-(--ink-3-aa)">
      Right-click anywhere in this box
    </div>
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuLabel>Frame</ContextMenuLabel>
    <ContextMenuItem icon={RiFileCopyLine}>Copy link</ContextMenuItem>
    <ContextMenuItem icon={RiDownloadLine}>Download original</ContextMenuItem>
    <ContextMenuSeparator />
    <ContextMenuItem icon={RiDeleteBinLine} destructive>Delete</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>
```

## Example — two doors

```tsx
<ContextMenu>
  <ContextMenuTrigger asChild>
    <div className="flex w-full max-w-sm items-center justify-between gap-3 rounded-(--radius) border border-(--rule) px-4 py-3">
      <Text size="sm" tone="strong" className="font-mono">
        kyoto-february.tif
      </Text>
      <DropdownMenu>
        <DropdownMenuTrigger asChild>
          <Button iconOnly aria-label="Actions for kyoto-february.tif" variant="ghost" size="sm">
            <RiMoreLine size={16} aria-hidden />
          </Button>
        </DropdownMenuTrigger>
        <DropdownMenuContent align="end">
          {ACTIONS.map((action) => (
            <DropdownMenuItem key={action.label} icon={action.icon} destructive={action.destructive}>
              {action.label}
            </DropdownMenuItem>
          ))}
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  </ContextMenuTrigger>
  <ContextMenuContent>
    {ACTIONS.map((action) => (
      <ContextMenuItem key={action.label} icon={action.icon} destructive={action.destructive}>
        {action.label}
      </ContextMenuItem>
    ))}
  </ContextMenuContent>
</ContextMenu>
```

## Example — a card trigger

```tsx
<ContextMenu>
  <ContextMenuTrigger asChild>
    <Card className="w-full max-w-sm">
      <CardHeader>
        <CardTitle as="h3">api.misoto22.com</CardTitle>
        <Badge tone="success">live</Badge>
      </CardHeader>
      <CardBody>
        Deployed from main four minutes ago. Right-click the card for its actions.
      </CardBody>
    </Card>
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem icon={RiExternalLinkLine}>Open the deployment</ContextMenuItem>
    <ContextMenuItem icon={RiPushpinLine}>Pin to the dashboard</ContextMenuItem>
    <ContextMenuItem icon={RiResetLeftLine} destructive>Roll back to the previous build</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>
```
