ContextMenu
A right-click / long-press menu anchored at the cursor. Built on Base UI's context-menu primitive — supports items, groups, labels, separators, sub-menus, checkbox-items, and radio-items. Compound API mirrors DropdownMenu so you can swap one for the other when the trigger gesture changes from click to right-click.
Playground
Installation
pnpm add @tessinaui/uiUsage
import {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuCheckboxItem,
ContextMenuRadioGroup,
ContextMenuRadioItem,
ContextMenuSub,
ContextMenuSubTrigger,
ContextMenuSubContent,
} from "@tessinaui/ui";<ContextMenu>
<ContextMenuTrigger asChild>
<div>Right-click anywhere in this region</div>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem leadingIcon={<Copy />} shortcut="⌘C">Copy</ContextMenuItem>
<ContextMenuItem leadingIcon={<Scissors />} shortcut="⌘X">Cut</ContextMenuItem>
<ContextMenuItem leadingIcon={<Edit />}>Rename</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem leadingIcon={<Trash2 />} intent="error" shortcut="⌘⌫">
Delete
</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>Showcase
When to use ContextMenu vs. DropdownMenu
| Use ContextMenu when… | Use DropdownMenu when… |
|---|---|
| The menu is invoked by right-click (or long-press on touch) | The menu is invoked by clicking a button |
| Actions apply to whatever the user clicked on (a row, file, region) | Actions are global / unrelated to a specific element |
| You want to keep the UI clean — no visible trigger required | You want a discoverable affordance (the button itself) |
| Examples: file explorer, code editor lines, table rows | Examples: avatar menu, settings dropdown, toolbar overflow |
Discoverability tradeoff (per Nielsen Norman Group): context menus are powerful but invisible. If the action is critical, also expose it through a visible affordance — a button, a kebab icon, or a keyboard shortcut shown in a tooltip.
Anatomy
<ContextMenu>
<ContextMenuTrigger>…</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuLabel />
<ContextMenuItem />
<ContextMenuCheckboxItem />
<ContextMenuRadioGroup>
<ContextMenuRadioItem />
</ContextMenuRadioGroup>
<ContextMenuSeparator />
<ContextMenuSub>
<ContextMenuSubTrigger />
<ContextMenuSubContent />
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>Trigger area
ContextMenuTrigger renders a <div> by default. The menu opens on right-click anywhere inside that region (or on long-press for touch). To use an existing element as the trigger, pass asChild:
<ContextMenuTrigger asChild>
<table>
<tbody>{/* … */}</tbody>
</table>
</ContextMenuTrigger>Size
ContextMenuContent accepts a size (xs | sm | md | lg | xl, default md) that scales item padding, font size, and the gap between items. Independent of rounded.
<ContextMenuContent size="sm">…</ContextMenuContent>Rounded
Standalone corner-radius axis matching the five-value enum used by AlertDialog, HoverCard, and Popover.
| Value | CSS |
|---|---|
"none" | rounded-none |
"sm" | rounded-md |
"md" (default) | rounded-lg |
"lg" | rounded-xl |
"full" | rounded-3xl |
<ContextMenuContent rounded="lg">…</ContextMenuContent>ContextMenuSubContent automatically inherits the same rounded value via context, so submenus' corners match the parent.
Width
ContextMenuContent also accepts width to bound the popup horizontally:
| Value | Behaviour |
|---|---|
"narrow" | max-w-[12rem] — short menus |
"default" (default) | bounded only by min-w-* from the size variant |
"wide" | min-w-[20rem] — long captions or descriptions |
Items
ContextMenuItem accepts:
| Prop | Type | Description |
|---|---|---|
leadingIcon | ReactNode | Icon on the start side |
trailingIcon | ReactNode | Icon on the end side (typically reserved for shortcut) |
shortcut | string | Keyboard shortcut badge (e.g. "⌘C") — pinned to the end |
description | string | Secondary line below the label |
intent | "none" | "error" | "error" styles destructive items in red |
inset | boolean | Indent to align with siblings that have indicators |
disabled | boolean | Greys out and disables the item |
Checkbox + radio items
<ContextMenuLabel>View</ContextMenuLabel>
<ContextMenuCheckboxItem checked={preview} onCheckedChange={setPreview}>
Show preview
</ContextMenuCheckboxItem>
<ContextMenuLabel>Sort by</ContextMenuLabel>
<ContextMenuRadioGroup value={sort} onValueChange={setSort}>
<ContextMenuRadioItem value="name">Name</ContextMenuRadioItem>
<ContextMenuRadioItem value="date">Date</ContextMenuRadioItem>
</ContextMenuRadioGroup>Sub-menus
<ContextMenuSub>
<ContextMenuSubTrigger leadingIcon={<Share2 />}>Share</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem>Copy link</ContextMenuItem>
<ContextMenuItem>Email</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>The chevron in ContextMenuSubTrigger flips automatically based on the content's dir (right-pointing in LTR, left-pointing in RTL).
RTL
Pass dir="rtl" on ContextMenuContent (or wrap in a dir="rtl" container). Sub-menu chevrons flip and inset spacing follows the reading direction.
<ContextMenuContent dir="rtl">
<ContextMenuItem leadingIcon={<Copy />}>نسخ</ContextMenuItem>
</ContextMenuContent>Accessibility
- Built on
@base-ui/react/context-menuwith W3C ARIA semantics: the popup usesrole="menu", items userole="menuitem"(ormenuitemcheckbox/menuitemradio). - Opens on right-click (
contextmenuevent) and long-press (touch). - Keyboard navigation: arrow keys move between items, Enter activates, Escape closes, sub-menus open on right-arrow (or left-arrow in RTL).
- The trigger is announced as an actionable region. The popup's items are announced one by one as focus moves.
- Use
intent="error"for destructive items so assistive tech surfaces the semantic colour via the rendered text. - Always offer a non-context-menu way to invoke critical actions (toolbar button, keyboard shortcut shown in a tooltip) — context menus are invisible until invoked.
API Reference
ContextMenu (root)
Re-exports BaseContextMenu.Root from Base UI. Accepts the standard Base UI ContextMenu Root props.
ContextMenuTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Render the children directly via Base UI's render prop instead of wrapping in a <div>. |
ContextMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Item padding, font size, and gap |
rounded | "none" | "sm" | "md" | "lg" | "full" | "md" | Popup corner radius (also inherited by ContextMenuSubContent) |
width | "narrow" | "default" | "wide" | "default" | Content width clamp |
sideOffset | number | 4 | Px gap between cursor anchor and popup |
dir | "ltr" | "rtl" | "ltr" | Reading direction |
ContextMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
leadingIcon | ReactNode | — | Icon at the start |
trailingIcon | ReactNode | — | Icon at the end |
shortcut | string | — | Keyboard shortcut badge |
description | string | — | Secondary line below the label |
intent | "none" | "error" | "none" | "error" for destructive |
inset | boolean | false | Indent to align with checkbox/radio indicators |
Sub-components
ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuCheckboxItem, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent, ContextMenuPortal, ContextMenuGroup — all accept className and the Base UI primitive's native props.
Notes
- Built on
@base-ui/react/context-menu— Base UI's name for the same primitive Radix calls ContextMenu and shadcn ships as the ContextMenu component. - Most parts (
Item,Group,Separator,CheckboxItem,RadioItem,SubmenuRoot,SubmenuTrigger) are reused from Base UI'sMenuprimitive — Base UI itself re-exports them. - The popup positions at the cursor on the side you'd expect: bottom-end of cursor in LTR, bottom-start in RTL.
Dropdown Menu
A contextual menu that opens from a trigger, built on Radix UI. Supports grouped items, leading/trailing icons, keyboard shortcuts, descriptions, submenus, checkboxes, radio groups, and full RTL layout.
Command
Command palette / ⌘K menu — filterable, keyboard-driven action launcher with inline and modal modes, grouped items, descriptions, breadcrumbs, shortcuts, and footer key hints.