Nasaq

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.

PreviewOpen ↗

Code

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>  );}

Overlays · stable

Live examples and controls: ContextMenu in the lab.

Install

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, 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.

Import

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

Quick start

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.

ExportCommon props
ContextMenuopen?, defaultOpen?, onOpenChange?, loopFocus?, disabled?
ContextMenuTriggerclassName?, render? (a div by default; give it a size)
ContextMenuRadioGroupvalue?, defaultValue?, onValueChange?
ContextMenuContentBase 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).

PropTypeDefaultDescription
variant?"default" | "danger""default"danger uses text-nq-danger-text.
shortcut?ReactNodenoneShortcut 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

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

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.

KeyAction
Right-click / long-press / Shift+F10 (browser)Opens the menu.
↑ / ↓Moves the highlight.
Enter / SpaceActivates the highlighted item.
→ (LTR) / ← (RTL) on a sub-triggerOpens the submenu.
EscCloses 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.

Lab

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

On this page