v1.0

Context Menu

Right-click menu that opens at the cursor over a region, for row actions, canvas commands, and editor shortcuts.

Pro

Description

ContextMenu is a right-click menu built on the same surface as Dropdown. It wraps a region in a ContextMenu.Trigger, and on contextmenu (right-click, long-press, or the keyboard menu key) it opens a ContextMenu.Popover at the pointer. Every part mirrors its Dropdown.* counterpart, so ContextMenu.Menu, ContextMenu.Section, ContextMenu.Header, ContextMenu.Item, and ContextMenu.Separator behave exactly as they do in a dropdown. size is set on the root and cascades to every item, label, and icon.

Reach for it when the actions belong to a thing on screen rather than to a button: file and list rows, canvas objects, table cells, an editable text field. Because the menu opens at the cursor, it keeps the pointer close to the target and does not need a visible trigger control.

Unlike a dropdown, there is no button to click: the affordance is the region itself, so give the trigger a hover state and make sure a keyboard path exists. Don't use a context menu as the primary way to reach an action (some users never right-click) and don't use it for form values (use Select) or for a labeled button menu (use Dropdown).

Because the parts alias Dropdown.*, they carry the same tiers: ContextMenu.Item (with a leading icon and size) and ContextMenu.Separator are free. Grouped sections and headers, a pinned search bar, smart items (secondary text, trailing shortcuts or controls), selection modes, and the footer slot are Pro (add it with a Pro seat via add context-menu).

Installation

pnpm dlx @create-ui/cli add context-menu

Anatomy

<ContextMenu>
  <ContextMenu.Trigger>
    <div>Right-click target</div>
  </ContextMenu.Trigger>
  <ContextMenu.Popover>
    <ContextMenu.Menu>
      <ContextMenu.Section>
        <ContextMenu.Header />
        <ContextMenu.Item />
        <ContextMenu.Separator />
      </ContextMenu.Section>
    </ContextMenu.Menu>
  </ContextMenu.Popover>
</ContextMenu>

Usage

import { ContextMenu } from "@/components/ui/context-menu"
<ContextMenu>
  <ContextMenu.Trigger>
    <div className="rounded-2xl border p-6">Right-click this area</div>
  </ContextMenu.Trigger>
  <ContextMenu.Popover className="w-52">
    <ContextMenu.Menu aria-label="Actions">
      <ContextMenu.Item id="copy">Copy</ContextMenu.Item>
      <ContextMenu.Item id="paste">Paste</ContextMenu.Item>
      <ContextMenu.Separator />
      <ContextMenu.Item id="delete" variant="danger">
        Delete
      </ContextMenu.Item>
    </ContextMenu.Menu>
  </ContextMenu.Popover>
</ContextMenu>

Examples

The free tier covers ContextMenu.Item (with a leading icon and size) and ContextMenu.Separator, as in the demo above. Everything richer is a Pro preview, marked with a Pro badge; the live demo still runs, but the source needs a Pro seat.

Grouping

The free tier separates runs of items with ContextMenu.Separator. Pro adds ContextMenu.Section + ContextMenu.Header for labeled groups. A destructive command takes variant="danger".

Pro

Sizes

Three sizes (xs, sm, md) set on the root cascade to radius, item height, label, and icon size. sm is the default.

On a text field

Wrap a focusable control and pass tabIndex={-1} to the trigger so it does not add a second tab stop. Capture the selection in onContextMenu before opening, because focus leaves the field once the menu is shown. Trailing keyboard-shortcut hints go in the item's trailing slot.

Pro

File list

Each row owns its own ContextMenu, so the actions operate on that row. A leading icon marks each command and a danger item removes the row, all with free-tier parts.

Accessibility

ContextMenu inherits React Aria's menu keyboard model once open. The native contextmenu event is dispatched by right-click, long-press, and the keyboard menu key (or Shift+F10), so make the trigger focusable and it works for keyboard users too. When the trigger already wraps a focusable control, pass tabIndex={-1} to avoid a duplicate tab stop.

KeyDescription
Menu / Shift+F10Opens the menu for the focused trigger.
↑ ↓Moves focus between items inside the open menu.
Home EndJumps to the first / last item.
A-ZType-ahead; focuses the first item matching the input.
Enter SpaceActivates the focused item.
EscCloses the menu and returns focus to the trigger.

ARIA notes:

  • The trigger renders with aria-haspopup="menu" and aria-expanded; React Aria wires role="menu" on the list and role="menuitem" (or menuitemcheckbox / menuitemradio under a selectionMode) on each row. The popover manages focus while the menu is open.
  • Keyboard-invoked events can report (0,0) coordinates; the trigger falls back to anchoring the menu to its own box in that case so it still opens sensibly.
  • Activating a row closes the menu by default. For a row that holds an interactive control, pass shouldCloseOnSelect={false}. Give a ContextMenu.Menu an aria-label when it has no visible label.
  • For the full focus, collection, and typeahead behavior, see the React Aria Menu docs.

Styling

Tailwind override: pass className to merge Tailwind classes on ContextMenu.Popover or any sub-part (merged via cn()):

<ContextMenu.Popover className="w-56" />

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="context-menu-trigger" on the trigger region, context-menu-anchor on the invisible cursor anchor, context-menu-popover on the surface, and context-menu-menu on the list. The remaining parts reuse the dropdown slots: dropdown-section, dropdown-header, dropdown-separator, dropdown-item, dropdown-item-label / dropdown-item-description / dropdown-item-container / dropdown-item-content / dropdown-item-indicator, and dropdown-footer.
  • data-size="xs" | "sm" | "md" mirrors the root size; data-variant="default" | "danger" on the item.
  • React Aria manages data-focused, data-selected, and data-disabled on each item; wrappers style through group-data-[focused]/item and group-data-[selected]/item.
  • The trigger carries cursor-context-menu; pass className (last-wins) to override it per instance, for example cursor-text over an editable field.

Target a state in CSS:

[data-slot="dropdown-item"][data-focused] {
  /* … */
}
  • Dropdown Menu: the same menu surface opened from a labeled button instead of a right-click.
  • Select: form-value picker bound to a Field. Use it when the chosen value is what the form submits.
  • Popover (Pro): arbitrary content with no menu semantics or roving focus.
  • Command (Pro): a searchable command palette in a modal shell.

API Reference

ContextMenu.* parts are aliases of the Dropdown.* parts, so their props match. The context-specific parts are the root, ContextMenu.Trigger, and ContextMenu.Popover.

ContextMenu

The root. Provides the size context, tracks the open state, and moves the cursor anchor. Its children are a ContextMenu.Trigger and a ContextMenu.Popover.

Props

PropTypeDefaultDescription
childrenReact.ReactNode-The ContextMenu.Trigger and ContextMenu.Popover.

Variants

VariantOptionsDefaultDescription
size"xs" "sm" "md""sm"Cascades to radius, item height, label, and icon size. Falls back to a surrounding Field size.

ContextMenu.Trigger

The right-click region. A focusable <div> (extends React.ComponentProps<"div">) that opens the menu on contextmenu and positions it at the pointer.

Props

PropTypeDefaultDescription
tabIndexnumber0Set -1 when the trigger wraps an already-focusable control to avoid a duplicate tab stop.
onContextMenu(e: MouseEvent) => void-Runs after the menu opens; use it to capture selection or state from the target.
classNamestring-Tailwind classes merged via cn(); last-wins over the default cursor-context-menu.
childrenReact.ReactNode-The region the menu is attached to.

ContextMenu.Popover

The floating surface. Opens at the cursor via a virtual anchor. Extends React Aria's PopoverProps; defaults to placement="bottom start" and offset={0}.

Props

PropTypeDefaultDescription
placementPlacement"bottom start"Preferred side/alignment relative to the cursor.
offsetnumber0Distance in pixels from the anchor.
classNamestring-Tailwind classes merged via cn() (commonly a width).
childrenReact.ReactNode-ContextMenu.Menu, plus optional ContextMenu.Footer.

ContextMenu.Menu

The list. Extends React Aria's MenuProps, so all collection and selection props (selectionMode, selectedKeys, onSelectionChange, disabledKeys, items, renderEmptyState, aria-label, …) pass through, identical to Dropdown.Menu.

ContextMenu.Item

A menu row. Aliases Dropdown.Item. A plain label with an optional leading icon, variant="danger" for a destructive row, and size are free; description, trailing, and indicator opt into the smart layout (Pro). id, textValue, onAction, isDisabled, and shouldCloseOnSelect pass through from React Aria's MenuItemProps.

Variants

VariantOptionsDefaultDescription
variant"default" "danger""default"danger renders a destructive (error) row.

ContextMenu.Separator

A thin divider between runs of items. Aliases Dropdown.Separator and, like ContextMenu.Item, is free. Extends React Aria's SeparatorProps; accepts className.

Other parts (Pro)

Aliases of the matching Dropdown parts; see the Dropdown Menu API for their props. Every part below is Pro, matching the Dropdown tiers.

PartRole
ContextMenu.SectionGroups related items under a shared label.
ContextMenu.HeaderThe overline label at the top of a section.
ContextMenu.MiscPins static content above the list.
ContextMenu.FooterA divided action bar below the list.
ContextMenu.ItemContainerWraps the media and text block of a row.
ContextMenu.ItemContentStacks the label and description.
ContextMenu.ItemLabelThe row's primary text.
ContextMenu.ItemDescriptionThe secondary line beneath the label.
ContextMenu.ItemIndicatorThe selection check shown in single/multi selection.