# Context Menu (/ui/components/react/context-menu)



<Playground component="context-menu" />

## Usage [#usage]

```tsx
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuGroupLabel,
  ContextMenuItem,
  ContextMenuSeparator,
} from '@appica/ui-react/context-menu'
```

```tsx
<ContextMenu>
  <ContextMenuTrigger className="flex h-40 w-72 items-center justify-center rounded-xl border border-dashed">
    Right-click here
  </ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuGroup>
      <ContextMenuGroupLabel>Edit</ContextMenuGroupLabel>
      <ContextMenuItem>Copy</ContextMenuItem>
      <ContextMenuItem>Cut</ContextMenuItem>
      <ContextMenuItem>Paste</ContextMenuItem>
    </ContextMenuGroup>
    <ContextMenuSeparator />
    <ContextMenuItem>Delete</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>
```

`ContextMenu` is a compound component built on [Base UI's Context Menu](https://base-ui.com/react/components/context-menu). Unlike a menu opened from a button, its trigger is the **target area** itself:

* **`ContextMenuTrigger`** — the region that opens the menu on right-click / long-press. Renders a `div` by default; pass `className` or `render` to wrap your own content.
* **`ContextMenuContent`** — the portalled popup, positioned at the pointer. Accepts the shared [floating props](/ui/components/react/popover) (`side`, `align`, `sideOffset`, …).
* **`ContextMenuItem`** — one action. Fires `onClick` and closes the menu by default.
* **`ContextMenuGroup`*&#x2A; / &#x2A;*`ContextMenuGroupLabel`*&#x2A; / &#x2A;*`ContextMenuSeparator`** — structure for sectioned lists.
* **`ContextMenuCheckboxItem`*&#x2A; / &#x2A;*`ContextMenuRadioGroup`*&#x2A; + &#x2A;*`ContextMenuRadioItem`** — stateful options with a check indicator.
* **`ContextMenuLinkItem`** — an item that renders an `<a>` for navigation.
* **`ContextMenuSub`*&#x2A; / &#x2A;*`ContextMenuSubTrigger`*&#x2A; / &#x2A;*`ContextMenuSubContent`** — a nested submenu.

Reach for a `ContextMenu` for actions tied to a **specific object** (a file, a row, a canvas) that users invoke by right-clicking it. For a menu opened from a visible button, use a [`DropdownMenu`](/ui/components/react/dropdown-menu) instead — and consider offering both for discoverability.

## Examples [#examples]

### Basic [#basic]

Right-click the area to open a standard edit menu with leading icons and trailing [`Kbd`](/ui/components/react/kbd) shortcuts.

```tsx
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
} from '@appica/ui-react/context-menu'
import { Kbd } from '@appica/ui-react/kbd'
import { Copy, Scissors, Clipboard, Trash } from '@appica/icons-react'

export default function ContextMenuDefault() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="bg-background-subtle border-border-strong flex h-40 w-72 items-center justify-center rounded-xl border border-dashed text-sm select-none">
        Right-click here
      </ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem>
          <Copy data-icon="start" />
          Copy
          <Kbd size="sm" className="ms-auto">
            ⌘C
          </Kbd>
        </ContextMenuItem>
        <ContextMenuItem>
          <Scissors data-icon="start" />
          Cut
          <Kbd size="sm" className="ms-auto">
            ⌘X
          </Kbd>
        </ContextMenuItem>
        <ContextMenuItem>
          <Clipboard data-icon="start" />
          Paste
          <Kbd size="sm" className="ms-auto">
            ⌘V
          </Kbd>
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem className="text-error-emphasis! data-highlighted:before:bg-error-subtle!">
          <Trash data-icon="start" />
          Delete
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  )
}
```

### On a card [#on-a-card]

The trigger can wrap any content — here a file card. Right-click it for object-specific actions, including a "Move to" submenu and a destructive delete.

```tsx
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
  ContextMenuSub,
  ContextMenuSubTrigger,
  ContextMenuSubContent,
} from '@appica/ui-react/context-menu'
import { Photo, Download, Pencil, Link, Folder, Star, Trash } from '@appica/icons-react'

export default function ContextMenuCard() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="block w-56 cursor-default text-left select-none">
        <div className="bg-background-muted text-foreground-subtle flex aspect-video items-center justify-center rounded-xl">
          <Photo className="size-8" />
        </div>
        <p className="text-foreground-intense mt-2 text-sm font-medium">cover-art.png</p>
        <p className="text-foreground-muted text-xs">1.2 MB · PNG</p>
      </ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem>
          <Download data-icon="start" />
          Download
        </ContextMenuItem>
        <ContextMenuItem>
          <Pencil data-icon="start" />
          Rename
        </ContextMenuItem>
        <ContextMenuItem>
          <Link data-icon="start" />
          Copy link
        </ContextMenuItem>
        <ContextMenuSub>
          <ContextMenuSubTrigger>
            <Folder data-icon="start" />
            Move to
          </ContextMenuSubTrigger>
          <ContextMenuSubContent>
            <ContextMenuItem>Projects</ContextMenuItem>
            <ContextMenuItem>Archive</ContextMenuItem>
            <ContextMenuItem>Trash</ContextMenuItem>
          </ContextMenuSubContent>
        </ContextMenuSub>
        <ContextMenuSeparator />
        <ContextMenuItem>
          <Star data-icon="start" />
          Add to favorites
        </ContextMenuItem>
        <ContextMenuItem className="text-error-emphasis! data-highlighted:before:bg-error-subtle!">
          <Trash data-icon="start" />
          Delete
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  )
}
```

### Checkbox & radio items [#checkbox--radio-items]

The same stateful items as `DropdownMenu`: `ContextMenuCheckboxItem` toggles booleans (and keeps the menu open), while a `ContextMenuRadioGroup` of `ContextMenuRadioItem`s picks one value.

```tsx
'use client'

import * as React from 'react'
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuGroupLabel,
  ContextMenuCheckboxItem,
  ContextMenuRadioGroup,
  ContextMenuRadioItem,
  ContextMenuSeparator,
} from '@appica/ui-react/context-menu'

export default function ContextMenuSelections() {
  const [showGrid, setShowGrid] = React.useState(true)
  const [showRulers, setShowRulers] = React.useState(false)
  const [zoom, setZoom] = React.useState('fit')

  return (
    <ContextMenu>
      <ContextMenuTrigger className="bg-background-subtle border-border-strong flex h-40 w-72 items-center justify-center rounded-xl border border-dashed text-sm select-none">
        Right-click the canvas
      </ContextMenuTrigger>
      <ContextMenuContent className="w-50">
        <ContextMenuGroup>
          <ContextMenuGroupLabel>View</ContextMenuGroupLabel>
          <ContextMenuCheckboxItem checked={showGrid} onCheckedChange={setShowGrid}>
            Show grid
          </ContextMenuCheckboxItem>
          <ContextMenuCheckboxItem checked={showRulers} onCheckedChange={setShowRulers}>
            Show rulers
          </ContextMenuCheckboxItem>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuRadioGroup value={zoom} onValueChange={setZoom}>
          <ContextMenuGroupLabel>Zoom</ContextMenuGroupLabel>
          <ContextMenuRadioItem value="fit">Fit to screen</ContextMenuRadioItem>
          <ContextMenuRadioItem value="100">100%</ContextMenuRadioItem>
          <ContextMenuRadioItem value="200">200%</ContextMenuRadioItem>
        </ContextMenuRadioGroup>
      </ContextMenuContent>
    </ContextMenu>
  )
}
```

### Sizes [#sizes]

`size` on the root scales the popup radius, item padding, and icon size — `sm`, `md` (default), or `lg`.

```tsx
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuSeparator,
} from '@appica/ui-react/context-menu'

const SIZES = ['sm', 'md', 'lg'] as const

export default function ContextMenuSizes() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-3">
      {SIZES.map((size) => (
        <ContextMenu key={size} size={size}>
          <ContextMenuTrigger className="bg-background-subtle border-border-strong flex h-24 w-32 items-center justify-center rounded-xl border border-dashed text-sm select-none">
            {size}
          </ContextMenuTrigger>
          <ContextMenuContent>
            <ContextMenuItem>Copy</ContextMenuItem>
            <ContextMenuItem>Paste</ContextMenuItem>
            <ContextMenuSeparator />
            <ContextMenuItem>Delete</ContextMenuItem>
          </ContextMenuContent>
        </ContextMenu>
      ))}
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and submenu direction — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

The menu opens at the pointer and grows toward the inline-start side, with submenus and chevrons mirrored. For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<Callout type="note" title="Scoped RTL needs the direction on the positioner">
  The popup portals to `<body>`, so its `start`/`end` alignment mirrors from the **document** direction (read from CSS,
  not from `DirectionProvider`) — automatic when `<html dir="rtl">`. If you instead scope RTL to a subtree of an LTR page,
  set it on the positioner so alignment still mirrors: `<ContextMenuContent positionerProps={{ dir: 'rtl' }}>`. See
  [Floating popups in a scoped RTL region](/ui/docs/react/rtl#floating-popups-in-a-scoped-rtl-region).
</Callout>

<RtlPreview component="context-menu" />

## API reference [#api-reference]

`ContextMenu` wraps [Base UI's Context Menu](https://base-ui.com/react/components/context-menu). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here — the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.

### ContextMenu [#contextmenu]

The root. Holds the open state and the shared `size`; renders no DOM of its own.

| Prop           | <ColMinWidth width="200">Type</ColMinWidth> | Default | <ColMinWidth width="220">Description</ColMinWidth>        |
| -------------- | ------------------------------------------- | ------- | --------------------------------------------------------- |
| `size`         | <Code>'sm' \| 'md' \| 'lg'</Code>           | `'md'`  | Scales the popup radius, item padding, and icon size.     |
| `open`         | `boolean`                                   | —       | Controlled open state. Pair with `onOpenChange`.          |
| `defaultOpen`  | `boolean`                                   | `false` | Uncontrolled initial open state.                          |
| `onOpenChange` | <Code>(open, eventDetails) => void</Code>   | —       | Fires when the menu opens or closes.                      |
| `disabled`     | `boolean`                                   | `false` | Stop the trigger area from opening a menu.                |
| `loopFocus`    | `boolean`                                   | `true`  | Wrap keyboard focus from the last item back to the first. |

### ContextMenuTrigger [#contextmenutrigger]

The target area. Right-click or long-press inside it opens the menu at the pointer. Renders a `div` by default and forwards every native prop.

| Prop        | <ColMinWidth width="220">Type</ColMinWidth>                 | Default | Description                             |
| ----------- | ----------------------------------------------------------- | ------- | --------------------------------------- |
| `render`    | <Code>ReactElement \| (props, state) => ReactElement</Code> | —       | Render the trigger as your own element. |
| `className` | `string`                                                    | —       | Extra classes on the trigger area.      |

### ContextMenuContent [#contextmenucontent]

The portalled, pointer-positioned popup wrapping the list. Defaults to `align="start"` and `sideOffset={2}`, and accepts the shared floating props.

| Prop               | <ColMinWidth width="160">Type</ColMinWidth>         | Default    | Description                                                     |
| ------------------ | --------------------------------------------------- | ---------- | --------------------------------------------------------------- |
| `side`             | <Code>'top' \| 'right' \| 'bottom' \| 'left'</Code> | `'bottom'` | Preferred side to grow toward from the pointer.                 |
| `align`            | <Code>'start' \| 'center' \| 'end'</Code>           | `'start'`  | Alignment along that side.                                      |
| `sideOffset`       | `number`                                            | `2`        | Gap between the pointer and the popup.                          |
| `alignOffset`      | `number`                                            | —          | Shift along the alignment axis.                                 |
| `collisionPadding` | `number`                                            | `5`        | Minimum gap kept from the viewport edge when flipping/shifting. |
| `container`        | <Code>HTMLElement \| ref \| null</Code>             | —          | Portal target — render the popup into a different node.         |
| `keepMounted`      | `boolean`                                           | `false`    | Keep the popup mounted in the DOM while closed.                 |
| `positionerProps`  | `object`                                            | —          | Escape hatch for the positioner element (`style`, `render`, …). |
| `portalProps`      | `object`                                            | —          | Escape hatch for the portal element (`style`, `render`, …).     |
| `className`        | `string`                                            | —          | Extra classes on the popup.                                     |

### ContextMenuItem [#contextmenuitem]

One action in the menu.

| Prop           | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                                                    |
| -------------- | ------------------------------------------- | ------- | -------------------------------------------------------------- |
| `closeOnClick` | `boolean`                                   | `true`  | Close the menu after the item is selected.                     |
| `disabled`     | `boolean`                                   | `false` | Make the item unselectable; skipped by keyboard and typeahead. |
| `onClick`      | <Code>(event) => void</Code>                | —       | Fires when the item is selected (click or Enter/Space).        |
| `className`    | `string`                                    | —       | Extra classes, merged via `tailwind-merge`.                    |

### ContextMenuCheckboxItem [#contextmenucheckboxitem]

A toggleable item with a check indicator. Keeps the menu open on click.

| Prop              | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                                            |
| ----------------- | ------------------------------------------- | ------- | ------------------------------------------------------ |
| `checked`         | `boolean`                                   | —       | Controlled checked state. Pair with `onCheckedChange`. |
| `defaultChecked`  | `boolean`                                   | `false` | Uncontrolled initial checked state.                    |
| `onCheckedChange` | <Code>(checked) => void</Code>              | —       | Fires when the checked state changes.                  |
| `closeOnClick`    | `boolean`                                   | `false` | Close the menu when toggled.                           |
| `disabled`        | `boolean`                                   | `false` | Make the item unselectable.                            |

### ContextMenuRadioGroup [#contextmenuradiogroup]

Wraps a set of mutually exclusive `ContextMenuRadioItem`s.

| Prop            | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                                           |
| --------------- | ------------------------------------------- | ------- | ----------------------------------------------------- |
| `value`         | `string`                                    | —       | Controlled selected value. Pair with `onValueChange`. |
| `defaultValue`  | `string`                                    | —       | Uncontrolled initial value.                           |
| `onValueChange` | <Code>(value) => void</Code>                | —       | Fires when the selection changes.                     |

### ContextMenuRadioItem [#contextmenuradioitem]

One option in a `ContextMenuRadioGroup`. Keeps the menu open on click.

| Prop           | Type      | Default | Description                   |
| -------------- | --------- | ------- | ----------------------------- |
| `value`        | `string`  | —       | The value this item selects.  |
| `closeOnClick` | `boolean` | `false` | Close the menu when chosen.   |
| `disabled`     | `boolean` | `false` | Make the option unselectable. |

### ContextMenuLinkItem [#contextmenulinkitem]

An item that renders an `<a>` for navigation. Forwards every native anchor prop (`href`, `target`, `rel`, …).

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `href`      | `string` | —       | Navigation target.                          |
| `className` | `string` | —       | Extra classes, merged via `tailwind-merge`. |

### ContextMenuGroup [#contextmenugroup]

Wraps one labelled section of items so a `ContextMenuGroupLabel` can name it.

| Prop        | Type        | Default | Description                                         |
| ----------- | ----------- | ------- | --------------------------------------------------- |
| `children`  | `ReactNode` | —       | A `ContextMenuGroupLabel` plus the section's items. |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`.         |

### ContextMenuGroupLabel [#contextmenugrouplabel]

The visible heading for a `ContextMenuGroup`. Size-aware; must be rendered inside a `ContextMenuGroup` (or `ContextMenuRadioGroup`).

| Prop        | Type        | Default | Description                                 |
| ----------- | ----------- | ------- | ------------------------------------------- |
| `children`  | `ReactNode` | —       | The label text.                             |
| `className` | `string`    | —       | Extra classes, merged via `tailwind-merge`. |

### ContextMenuSeparator [#contextmenuseparator]

A thin divider between items or groups.

| Prop        | Type     | Default | Description                                 |
| ----------- | -------- | ------- | ------------------------------------------- |
| `className` | `string` | —       | Extra classes, merged via `tailwind-merge`. |

### ContextMenuSub [#contextmenusub]

Wraps a nested submenu. Forwards Base UI's `ContextMenu.SubmenuRoot`.

| Prop          | <ColMinWidth width="180">Type</ColMinWidth> | Default | Description                           |
| ------------- | ------------------------------------------- | ------- | ------------------------------------- |
| `open`        | `boolean`                                   | —       | Controlled open state of the submenu. |
| `defaultOpen` | `boolean`                                   | `false` | Uncontrolled initial open state.      |

### ContextMenuSubTrigger [#contextmenusubtrigger]

The item that opens a submenu; renders a trailing chevron. Submenus open on hover by default.

| Prop          | Type      | Default | Description                                 |
| ------------- | --------- | ------- | ------------------------------------------- |
| `openOnHover` | `boolean` | `true`  | Open the submenu on hover (and on click).   |
| `delay`       | `number`  | `100`   | Hover-open delay in ms.                     |
| `closeDelay`  | `number`  | `0`     | Hover-close delay in ms.                    |
| `className`   | `string`  | —       | Extra classes, merged via `tailwind-merge`. |

### ContextMenuSubContent [#contextmenusubcontent]

The submenu popup. Defaults to opening on the `inline-end` side; otherwise identical to `ContextMenuContent`.

| Prop        | Type     | Default | Description                         |
| ----------- | -------- | ------- | ----------------------------------- |
| `className` | `string` | —       | Extra classes on the submenu popup. |

## Accessibility [#accessibility]

* The popup has `role="menu"`; items are `menuitem`, `menuitemcheckbox`, or `menuitemradio`.
* Opens on `contextmenu` (right-click) or long-press; **Shift + F10** and the dedicated **Menu** key open it from the keyboard when the trigger is focused.
* **↑/↓*&#x2A; move the highlight, &#x2A;*→/←** open and close submenus, **Enter** selects, **Escape** closes, and typeahead jumps to matching items.
* Focus moves into the popup on open and returns to the triggering element on close.
* `disabled` items are skipped by keyboard navigation and typeahead.
* Popup enter/exit transitions are skipped when the user prefers reduced motion.
