# ContextMenu

> Right-click (or long-press) menu for a region, with items, checkbox and radio items, groups, shortcuts and submenus. Wraps Base UI ContextMenu and shares DropdownMenu styling.

Source: https://docs.nasaqui.com/components/context-menu

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/context-menu.json
```

A menu that opens at the pointer when the user right-clicks (or long-presses on touch) a region. It looks and
behaves like a [`DropdownMenu`](https://docs.nasaqui.com/components/dropdown-menu), sharing its item styles, but is anchored to a
region instead of a button.

## When to use

- Secondary actions on a row, card, canvas or file area, as a shortcut alongside a visible route.

## When not to use

- The only path to an action: context menus are hidden by nature. Offer a visible button or `DropdownMenu` too.
- Menus from a button: use a [`DropdownMenu`](https://docs.nasaqui.com/components/dropdown-menu).

## Import

```tsx
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuItem } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@fadymondy/nasaq/web";
import { Copy, Trash2 } from "lucide-react";

export function FileRow() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="rounded-control border border-border p-4">report.pdf</ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem shortcut="⌘C">
          <Copy /> Copy
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="danger">
          <Trash2 /> Delete
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  );
}
```

## Anatomy

```
ContextMenu                         Base UI ContextMenu.Root
├─ ContextMenuTrigger               the region that listens for right-click / long-press
└─ ContextMenuContent               Portal + positioner + popup   data-slot="context-menu-content"
   ├─ ContextMenuGroup
   │  ├─ ContextMenuLabel           data-slot="context-menu-label"
   │  └─ ContextMenuItem            data-slot="context-menu-item", data-variant="default|danger"
   │     └─ ContextMenuShortcut     data-slot="dropdown-menu-shortcut" (from the shortcut prop)
   ├─ ContextMenuSeparator          data-slot="context-menu-separator"
   ├─ ContextMenuCheckboxItem       data-slot="context-menu-checkbox-item"
   ├─ ContextMenuRadioGroup
   │  └─ ContextMenuRadioItem       data-slot="context-menu-radio-item"
   └─ ContextMenuSub
      ├─ ContextMenuSubTrigger      data-slot="context-menu-sub-trigger" (chevron added)
      └─ ContextMenuSubContent      data-slot="context-menu-sub-content"
```

## API

Aliases: `ContextMenu` = `ContextMenu.Root`, `ContextMenuTrigger` = `ContextMenu.Trigger`,
`ContextMenuGroup` = `ContextMenu.Group`, `ContextMenuRadioGroup` = `ContextMenu.RadioGroup`,
`ContextMenuSub` = `ContextMenu.SubmenuRoot`, `ContextMenuShortcut` = `DropdownMenuShortcut`.

| Export | Common props |
| --- | --- |
| `ContextMenu` | `open?`, `defaultOpen?`, `onOpenChange?`, `loopFocus?`, `disabled?` |
| `ContextMenuTrigger` | `className?`, `render?` (a `div` by default; give it a size) |
| `ContextMenuRadioGroup` | `value?`, `defaultValue?`, `onValueChange?` |
| `ContextMenuContent` | Base UI `ContextMenu.Popup` props (`className`). It opens at the pointer; side and align are chosen automatically. |

### `ContextMenuItem`

`ContextMenuItemProps` is the same type as `DropdownMenuItemProps` (Base UI `Menu.Item` props).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant?` | `"default" \| "danger"` | `"default"` | `danger` uses `text-nq-danger-text`. |
| `shortcut?` | `ReactNode` | none | Shortcut hint at the inline end. |

### Other parts

- `ContextMenuCheckboxItem`: `checked?`, `defaultChecked?`, `onCheckedChange?`, `disabled?`.
- `ContextMenuRadioItem`: `value` (required), `disabled?`. Inside `ContextMenuRadioGroup`.
- `ContextMenuLabel`: must be inside a `ContextMenuGroup`.
- `ContextMenuSeparator`: a 1px line bleeding to the popup edges.
- `ContextMenuSubContent`: `side?` (default `"inline-end"`), `align?` (`"start"`), `sideOffset?` (`-4`).

## Examples

### Toggles and a submenu

```tsx
import {
  ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem,
  ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,
} from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Board() {
  const [pinned, setPinned] = useState(false);
  return (
    <ContextMenu>
      <ContextMenuTrigger className="h-40 rounded-card border border-dashed border-border p-6">Right-click</ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuCheckboxItem checked={pinned} onCheckedChange={setPinned}>Pin to top</ContextMenuCheckboxItem>
        <ContextMenuSub>
          <ContextMenuSubTrigger>Sort by</ContextMenuSubTrigger>
          <ContextMenuSubContent>
            <ContextMenuItem>Last updated</ContextMenuItem>
            <ContextMenuItem>Priority</ContextMenuItem>
          </ContextMenuSubContent>
        </ContextMenuSub>
      </ContextMenuContent>
    </ContextMenu>
  );
}
```

### Arabic

```tsx
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from "@fadymondy/nasaq/web";
import { Pencil } from "lucide-react";

export function BoardAr() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="h-40 rounded-card border border-dashed border-border p-6">انقر بزر الفأرة الأيمن</ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem>
          <Pencil /> تعديل
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  );
}
```

## ContextMenuActions

`<ContextMenuActions actions={[{ id, label, icon, onSelect, danger, group }]} render={<li />} />` turns any element into a context-menu trigger from a plain action list. It is what DataTable, EntityList, Inbox, FileExplorer, KanbanBoard and NotificationCenter use for their rows and cards. Right-click, Shift+F10 and the Menu key open it (from the keyboard at the element's inline start); Escape returns focus to the element (`focusTarget` picks another). Inputs, textareas, links, contenteditable and Shift+right-click keep the browser menu. Empty `actions` or `disabled` render the element untouched.

## Accessibility

Provided by Base UI: the popup has `role="menu"` and items `menuitem` / `menuitemcheckbox` / `menuitemradio`. Focus
moves into the menu on open and returns on close.

| Key | Action |
| --- | --- |
| Right-click / long-press / `Shift+F10` (browser) | Opens the menu. |
| `↑` / `↓` | Moves the highlight. |
| `Enter` / `Space` | Activates the highlighted item. |
| `→` (LTR) / `←` (RTL) on a sub-trigger | Opens the submenu. |
| `Esc` | Closes the menu. |

- The trigger region is not focusable by itself, so mirror every action in a visible, focusable control.
- Localise item text and labels.

## RTL & i18n

- Check/radio indicators use `start-2.5` / `ps-8`; shortcut and chevron use `ms-auto`. All flip with `dir`.
- Submenus open at `inline-end`, so they open to the left in RTL. The chevron mirrors.
- Shortcut text is forced `dir="ltr"`.
- No built-in strings.

## Styling & tokens

- Same tokens as DropdownMenu (shared internal class lists): `bg-popover`, `border-border`, `rounded-floating`, `shadow-floating`, `rounded-control`, `h-nav-row`, `data-highlighted:bg-nq-selected`.
- Danger: `text-nq-danger-text`. Target `[data-slot=context-menu-item][data-variant=danger]`.
- Extend with `className`; no raw hex.

## Do / Don't

- **Do** mirror context-menu actions in a visible control.
- **Do** put destructive items last, after a separator.
- **Don't** nest submenus more than one level.
- **Don't** use it on touch-only surfaces as the sole route to an action.

## Related

- [DropdownMenu](https://docs.nasaqui.com/components/dropdown-menu) · [Popover](https://docs.nasaqui.com/components/popover) · [CommandPalette](https://docs.nasaqui.com/components/command-palette)

## Lab

https://docs.nasaqui.com/?path=/docs/components-overlays-context-menu--docs

## Code

### React

```tsx
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@fadymondy/nasaq/web";
import { Copy, Trash2 } from "lucide-react";

export function FileRow() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="rounded-control border border-border p-4">report.pdf</ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem shortcut="⌘C">
          <Copy /> Copy
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="danger">
          <Trash2 /> Delete
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  );
}
```

### shadcn

```tsx
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "@/components/ui/context-menu";
import { Copy, Trash2 } from "lucide-react";

export function FileRow() {
  return (
    <ContextMenu>
      <ContextMenuTrigger className="rounded-control border border-border p-4">report.pdf</ContextMenuTrigger>
      <ContextMenuContent>
        <ContextMenuItem shortcut="⌘C">
          <Copy /> Copy
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="danger">
          <Trash2 /> Delete
        </ContextMenuItem>
      </ContextMenuContent>
    </ContextMenu>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { Copy, Trash2 } from "lucide-vue-next";
import { NqContextMenu, NqContextMenuContent, NqContextMenuItem, NqContextMenuSeparator, NqContextMenuTrigger } from "@fadymondy/nasaq/vue";
</script>

<template>
  <NqContextMenu>
    <NqContextMenuTrigger class="rounded-control border border-border p-4">report.pdf</NqContextMenuTrigger>
    <NqContextMenuContent>
      <NqContextMenuItem shortcut="⌘C"><Copy /> Copy</NqContextMenuItem>
      <NqContextMenuSeparator />
      <NqContextMenuItem variant="danger"><Trash2 /> Delete</NqContextMenuItem>
    </NqContextMenuContent>
  </NqContextMenu>
</template>
```

### Blade

```blade
<x-nq::context-menu>
    <x-nq::context-menu.trigger class="rounded-control border border-border p-4">report.pdf</x-nq::context-menu.trigger>
    <x-nq::context-menu.content>
        <x-nq::context-menu.item shortcut="⌘C"><x-lucide-copy /> Copy</x-nq::context-menu.item>
        <x-nq::context-menu.separator />
        <x-nq::context-menu.item variant="danger"><x-lucide-trash-2 /> Delete</x-nq::context-menu.item>
    </x-nq::context-menu.content>
</x-nq::context-menu>
```

### HTML + Alpine

```html
<div data-slot="context-menu" x-data="nqContextMenu(false)" x-modelable="open" class="contents"><div data-slot="context-menu-trigger" x-bind="trigger" x-ref="trigger" class="rounded-control border border-border p-4">report.pdf</div>
    <template x-teleport="body">
    <div data-slot="context-menu-content" x-bind="popup" x-init="popupEl = $el" x-nq-presence="open"
        class="fixed z-50 min-w-44 overflow-hidden rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none max-h-[var(--available-height)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"><div data-slot="context-menu-item" role="menuitem" data-variant="default" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
  <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg> Copy<span data-slot="context-menu-shortcut" dir="ltr" class="ms-auto font-mono text-[11px] text-muted-foreground">⌘C</span>
</div>
        <div data-slot="context-menu-separator" role="separator" aria-orientation="horizontal" class="-mx-1.5 my-1.5 h-px bg-border"></div>
        <div data-slot="context-menu-item" role="menuitem" data-variant="danger" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 text-nq-danger-text [&_svg]:text-current"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M10 11v6"/>
  <path d="M14 11v6"/>
  <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/>
  <path d="M3 6h18"/>
  <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg> Delete</div></div>
</template></div>
```
