Noctis

Context menu

A right-click menu: a ContextMenu.Trigger area opens a floating list of actions at the pointer, replacing the browser's native context menu. It shares the Menu row family — items, groups, separators, submenus, and shortcuts — so a context menu reads identically to a dropdown.

Basic

A ContextMenu.Trigger marks the area that responds to a right-click (or long-press on touch); the ContextMenu.Content holds the rows. The trigger is focusable by default and ships visually neutral — style it yourself to signal that it's interactive.

Right-click here

Acting on the right-clicked target

What makes a context menu contextual is that its actions operate on whatever was right-clicked. Give each target its own ContextMenu.Root and close over that target's data in the item handlers — here every file row opens a menu whose actions reference that file.

  • Q3 report.pdf
  • budget.xlsx
  • notes.md
  • logo.svg
Right-click a file to act on it.

Groups

Wrap related items in a ContextMenu.Group with a ContextMenu.GroupLabel, divide sections with a ContextMenu.Separator, and add a trailing ContextMenu.Shortcut for keyboard hints. A destructive action takes tone="danger": a red label whose highlight tints toward danger.

Right-click here

Inset alignment

Mixing iconed and icon-less rows leaves the labels ragged. Add inset to a row without a glyph to reserve the leading icon column, so every label lines up.

Right-click here

Checkbox & radio items

ContextMenu.CheckboxItem toggles a setting; ContextMenu.RadioGroup with ContextMenu.RadioItems selects exactly one value. Both keep the menu open when activated and reserve the leading column for their indicator, so labels stay aligned.

Right-click here

Nest a ContextMenu.SubmenuRoot wrapping a ContextMenu.SubmenuTrigger and its own ContextMenu.Content. The submenu opens beside its trigger on the inline-end side, flipping to inline-start at the viewport edge, and the trailing chevron mirrors under RTL. It opens on hover after a short intent delay and from the keyboard with Right arrow or Enter. Keep nesting to a single level for keyboard predictability.

Right-click here

ContextMenu.LinkItem renders a real <a> for the canonical "Open in new tab" / "Open link" rows — it carries the same row recipe as Item (leading icon, inset, tone) and accepts anchor props (href, target, rel). Navigating is a terminal action, so a link closes the menu by default (closeOnClick={false} keeps it open).

Right-click here

The trigger — focusable & armed

The trigger is a real, focusable part. It defaults to tabIndex={0} so it can be reached with Tab and opened from the keyboard, and context-menu.css gives it a :focus-visible ring. Base UI stamps data-pressed while a touch long-press registers (a neutral instant tint confirms the hold) and data-popup-open while the menu is open — a stable seam you can paint an "armed" state from. On touch, the long-press opens the Noctis menu instead of the OS callout or a text selection.

Focus me and press Shift+F10

Keyboard

A context menu is opened by pointer (right-click), by touch (long-press), or from the keyboard with Shift+F10 / the Menu key at the focused trigger; once open it is fully keyboard-operable.

KeyAction
Shift + F10 / MenuOpen the menu at the focused trigger.
/ Move the highlight to the next / previous item.
Home / EndFirst / last item.
Open the highlighted submenu and move into it ( under RTL).
Close the current submenu and return to its trigger ( under RTL).
Enter / SpaceActivate the highlighted item — run an Item, toggle a CheckboxItem, select a RadioItem, open a submenu.
EscClose the current menu level; on the root, return focus to the trigger area.
TabClose the whole menu and move focus on.
CharactersTypeahead — jump to the item whose label matches what you type.

Disabled items stay reachable by the keyboard and are announced as disabled — they just can't be activated. That follows the ARIA menu pattern: removing them from navigation would hide why an action is unavailable from screen-reader users.

Accessibility

  • Never the sole entry point. A right-click menu is discoverable only by trying it, so every action it offers must also be reachable another visible way — a toolbar button, a Menu, a keyboard shortcut. Treat the context menu as an accelerator, not the only door to an action.
  • Keep the trigger focusable. The default tabIndex={0} is what lets Shift+F10 open the menu; if you compose your own render element, make sure it's focusable too. An unfocusable trigger has no keyboard entry point.
  • Expose live shortcuts. ContextMenu.Shortcut is decorative (hidden from assistive tech so the item's name stays clean). When the binding is actually live, also set aria-keyshortcuts on the item — e.g. <ContextMenu.Item aria-keyshortcuts="Control+C">Copy<ContextMenu.Shortcut keys="Mod+C" /></ContextMenu.Item> — so the shortcut reaches screen-reader users.

Content conventions

A few conventions keep menus scannable and predictable:

  • Verb + noun, in title case — "Copy link", "Move to trash", not a bare "Copy" where the object is ambiguous.
  • An ellipsis () ends a label that opens a follow-up dialog rather than acting immediately — "Rename…", "Move to…".
  • Group destructive actions at the bottom, behind a Separator, and mark them tone="danger" so a slip doesn't land on them.
  • One nesting level. Deeper submenus are hard to reach with a pointer and unpredictable from the keyboard — flatten instead.

A context menu is always modal — while it's open, page scroll is locked and outside content is inert. There is no non-modal variant: Base UI ignores modal on a context menu (it's always a "context-menu" parent), and passing it is a no-op that logs a warning.

The scroll lock reserves the scrollbar gutter (scrollbar-gutter: stable on <html>) as it hides the bar, so opening the menu doesn't shift the page. If the bar visually disappearing still bothers you, reserve the gutter for the whole app so the page looks identical with the menu open:

CSS
html {
    scrollbar-gutter: stable both-edges;
}

Anatomy

Compose a context menu from its parts. ContextMenu.Root owns the open state (it accepts every Base UI ContextMenu.Root prop — open, defaultOpen, onOpenChange).

  • ContextMenu.Root — owns the open state; renders no element of its own. Always modal — page scroll is locked and outside content is inert while open. Unlike Menu there is no non-modal variant: Base UI deliberately ignores modal on a context menu (it's always a "context-menu" parent), so passing it is a no-op that logs a warning. Useful passthrough knobs: disabled (suppress the menu on a region so the browser's native menu shows instead), onOpenChangeComplete (fires after the open/close transition settles), and closeParentOnEsc (when nested, also close the parent on Escape).
  • ContextMenu.Trigger — the area that opens the menu on right-click / long-press, suppressing the browser's native menu. Focusable by default (tabIndex={0}) so Shift+F10 works; style it directly or compose any element through render. Exposes a data-pressed / data-popup-open state seam.
  • ContextMenu.Content — the floating, elevated, animated surface holding the items. Props: side (default bottom; inline-end inside a submenu), align (default start), sideOffset, alignOffset, collisionPadding.
  • ContextMenu.Group + ContextMenu.GroupLabel — a labelled section of related items.
  • ContextMenu.Item — a menu action with an optional leading icon, an inset alignment helper, and a tone (default | danger).
  • ContextMenu.LinkItem — a navigable row rendering an <a> (href, target, rel), with the same icon / inset / tone recipe as Item; closes the menu on click by default.
  • ContextMenu.CheckboxItem — a toggling item with an animated check indicator.
  • ContextMenu.RadioGroup + ContextMenu.RadioItem — a single-select set with a dot indicator.
  • ContextMenu.SubmenuRoot + ContextMenu.SubmenuTrigger — a nested submenu; the trigger is an item-shaped row with a trailing, RTL-mirrored chevron.
  • ContextMenu.Shortcut — a trailing, decorative keyboard hint composing Kbd.
  • ContextMenu.Separator — a hairline between groups of items.

The right-click-specific parts carry their own data-slot (context-menu-trigger, context-menu-content, context-menu-viewport); the shared row family reuses Menu's slots (menu-group, menu-group-label, menu-item, menu-link-item, menu-checkbox-item, menu-radio-group, menu-radio-item, menu-item-indicator, menu-submenu-trigger, menu-shortcut, menu-separator). Pair a slot with the Base UI state attributes (data-highlighted, data-disabled, data-checked) for host-side styling.

Design tokens

Generated from the component's declaration — the same graph that mints the CSS, so a variable name or its resolution default can't drift. The minted tokens are the public override seam for the popup; set one on any ancestor and every context menu in that region retunes. The popup's min-width and viewport-padding are kept value-identical to the Menu overlay, so the two read the same — and the shared rows are tuned through the Menu tokens. See Customization for the full override ladder and Tokens for the whole graph.

Token

API reference

Generated from the component's types — every prop, type, default, and description comes straight from the source. Each part gets its own table; parts that only forward to Base UI list just the props they pass through.

ContextMenu.Root

No props of its own — forwards to the underlying Base UI part.

ContextMenu.Trigger

Prop

ContextMenu.Content

Prop

ContextMenu.Group

Prop

ContextMenu.GroupLabel

Prop

ContextMenu.Item

Prop

ContextMenu.LinkItem

Prop

ContextMenu.CheckboxItem

Prop

ContextMenu.RadioGroup

Prop

ContextMenu.RadioItem

Prop

ContextMenu.SubmenuRoot

No props of its own — forwards to the underlying Base UI part.

ContextMenu.SubmenuTrigger

Prop

ContextMenu.Shortcut

Prop

ContextMenu.Separator

Prop

AttributeDescription
data-slotThe trigger area element.
data-pressedPresent on the trigger while a touch long-press is registering — paints the neutral pressed tint.
data-popup-openPresent on the trigger while its menu is open — the off-by-default "armed" tint hook.
data-sideThe side of the anchor the content actually rendered on (`bottom`, `inline-end`, …).
data-starting-stylePresent on the content for the first frame after mount — the transition's start state.
data-ending-stylePresent on the content while it transitions out before unmounting.
data-instantPresent on the content when the change should not animate (keyboard open, dismissal).