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

Loading example…
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.
Home
Moves to the first item.
End
Moves to the last item.
↵
Activates the focused item.
Space
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
openbooleanControlled open state.
defaultOpenbooleanInitial 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
classNamestringPosition the popover from the exposed CSS variables; no placement is applied for you.
styleCSSPropertiesPosition 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.

ContextMenuTrigger

Style hookMeaning
[data-open]The menu is open.

MenuPopover

Style hookMeaning
[data-open]The menu is open.
:popover-openNative pseudo-class equivalent.
--comp0-context-menu-x/yThe recorded pointer position in px, for your positioning CSS.

MenuItem

Style hookMeaning
:focus-visibleThe item has visible keyboard focus.

Keep exploring