Skip to content

Navigation

Context Menu

A menu opened from a right click instead of a button.

When to use it: Use it for secondary actions on an object that already has a primary interaction.

On this page

Example

Right-click here (or press Shift+F10)
Context Menu.tsxtsx
import { MenuItem, MenuList, MenuPopover, MenuSeparator } from "@comp0/react";
import { ContextMenu, ContextMenuTrigger } from "@comp0/react";

export function Example() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        tabIndex={0}
        className="grid h-36 w-full max-w-md place-items-center rounded border border-dashed border-zinc-950/20 text-base text-zinc-500 outline-teal-600 select-none focus-visible:outline-2 data-open:border-teal-600 sm:text-sm dark:border-white/20 dark:text-zinc-400 dark:outline-teal-400 dark:data-open:border-teal-400"
      >
        Right-click here (or press Shift+F10)
      </ContextMenuTrigger>
      {/* The popover positions itself: the recorded pointer position arrives
          as --comp0-context-menu-x/y px values on the popover element. */}
      <MenuPopover className="fixed inset-auto top-(--comp0-context-menu-y) left-(--comp0-context-menu-x) m-0 w-44 rounded border-0 bg-white p-1 shadow-lg ring-1 ring-zinc-950/10 dark:bg-zinc-900 dark:shadow-none dark:ring-white/10">
        <MenuList aria-label="Attachment actions">
          <MenuItem
            className="w-full rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800"
            value="download"
          >
            Download
          </MenuItem>
          <MenuItem
            className="w-full rounded px-3 py-2.5 text-left text-base text-zinc-800 select-none hover:bg-zinc-100 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800"
            value="rename"
          >
            Rename
          </MenuItem>
          <MenuSeparator className="my-1 h-px bg-zinc-950/10 dark:bg-white/10" />
          <MenuItem
            className="w-full rounded px-3 py-2.5 text-left text-base text-red-700 select-none hover:bg-red-50 sm:py-2 sm:text-sm dark:text-red-400 dark:hover:bg-red-950"
            value="remove"
          >
            Remove
          </MenuItem>
        </MenuList>
      </MenuPopover>
    </ContextMenu>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. ContextMenu

    Open-state provider without a trigger button; it records the pointer position and restores focus on close. Does not add a DOM element.

  2. ContextMenuTrigger

    The right-clickable area: a plain div that opens the menu at the pointer on contextmenu, or from the keyboard with Shift+F10. Owns a DOM element.

  3. MenuPopover

    Floating surface. The recorded position arrives as --comp0-context-menu-x/y px values on this element; anchor it yourself with position: fixed; left: var(--comp0-context-menu-x); top: var(--comp0-context-menu-y). Owns a DOM element.

  4. MenuListOptional

    Menu collection inside the positioned surface. Owns a DOM element.

  5. MenuItem

    Action item. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Wrap the right-clickable area in ContextMenuTrigger inside a ContextMenu.

  2. 2

    Add the supporting parts

    Put a labelled MenuList inside MenuPopover; no button labels the list for you.

  3. 3

    Make the behavior clear

    Anchor the popover at the pointer yourself: position: fixed with left/top from the exposed --comp0-context-menu-x/y variables.

    Exampletsx
    <ContextMenu><ContextMenuTrigger tabIndex={0}>Attachment</ContextMenuTrigger><MenuPopover style={{ position: "fixed", inset: "auto", margin: 0, left: "var(--comp0-context-menu-x)", top: "var(--comp0-context-menu-y)" }}><MenuList aria-label="Attachment actions"><MenuItem>Download</MenuItem></MenuList></MenuPopover></ContextMenu>

Keyboard

F10
Opens the menu at the focused element. · trigger area
Opens the menu at the focused element. · trigger area
Moves to the next item.
Moves to the previous item.
Activates the focused item.
Esc
Closes and restores focus to where it was.
Closes the menu and moves on.

Forms and accessibility

No native form behavior.

Accessibility checklist

  • Give the MenuList an aria-label; a context menu has no trigger button to borrow a name from.
  • Keep the trigger area keyboard-reachable (tabIndex={0}) so Shift+F10 can open the menu without a mouse.
  • Offer the same actions somewhere visible; right-click alone is not discoverable.

API reference

Importtsx
import { ContextMenu, ContextMenuTrigger, MenuItem, MenuList, MenuPopover } from "@comp0/react";

ContextMenu

Context only

Open-state provider without a trigger button; it records the pointer position and restores focus on close.

PropTypeDescription
open / defaultOpenbooleanControlled or initial open state.
onToggle(open: boolean) => voidReceives the next open state.

ContextMenuTrigger

DOM element

The right-clickable area: a plain div that opens the menu at the pointer on contextmenu, or from the keyboard with Shift+F10.

PropTypeDescription
tabIndexnumberGive the area (or something inside it) a tab stop so keyboard users can reach the menu.

MenuPopover

DOM element

Floating surface. The recorded position arrives as --comp0-context-menu-x/y px values on this element; anchor it yourself with position: fixed; left: var(--comp0-context-menu-x); top: var(--comp0-context-menu-y).

PropTypeDescription
className / stylestring | CSSPropertiesPosition the popover from the exposed CSS variables; no placement is applied for you.

MenuList

OptionalDOM element

Menu collection inside the positioned surface.

PropTypeDescription
aria-labelstringNames the menu; required because no trigger button labels it.

MenuItem

DOM element

Action item.

PropTypeDescription
onClick(event) => voidRuns the action; preventDefault keeps the menu open.
valuestringOptional identity for typeahead and data-value.
disabledbooleanDisables the action.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

[data-open]

on ContextMenuTrigger, MenuPopover

The menu is open.
:popover-open

on MenuPopover

Native pseudo-class equivalent.
--comp0-context-menu-x/y

on MenuPopover

The recorded pointer position in px, for your positioning CSS.
:focus-visible

on MenuItem

The item has visible keyboard focus.

Keep exploring