When to Use
Use ContextMenu when actions belong to the exact thing a user right-clicked,
such as a file, canvas object, or table row. Do not make it the only way to
reach important actions because context menus are easy to miss. Use Menu or
DropdownMenu when a visible button should open the actions, and Menubar for a
persistent application command bar.
Features
The behavior Atom owns before your product adds appearance.
- Opens at right-click coordinates or the keyboard trigger location.
- Re-invokes at the latest right-click point and transfers an open menu directly
between registered ContextMenu targets without exposing the browser menu.
- Opens from a cancel-safe 700 ms touch/pen long press with a 10 px tolerance.
- Supports controlled state, modal behavior, looping, and dismissal settings.
- Includes actions, checkbox choices, radio choices, groups, separators, and nested menus.
- Positions and collision-adjusts Content with Floating UI.
- Supports pointer highlight, keyboard navigation, typeahead, and RTL submenus.
- Keeps nested portalled menus inside parent modal focus scopes.
Import
Anatomy
API Reference
Root
Owns Menu state and the point used to position Content. It renders no wrapper.
open | boolean | - |
defaultOpen | boolean | false |
onOpenChange | (open: boolean) => void | - |
modal | boolean | true |
closeOnSelect | boolean | true |
loop | boolean | true |
closeOnEscape | boolean | true |
Trigger
Wraps the area that owns the menu. It renders a span with display: contents
by default, opens on the context-menu gesture, and does not add button
semantics to the wrapped area.
Touch and pen track one primary long press. Movement beyond 10 px, scrolling,
early release, cancellation, a second pointer, disabled state, unmount, or a
native contextmenu event cancels the fallback.
While a ContextMenu is open, another secondary click inside its Trigger updates
the point anchor and keeps the custom menu active. A secondary click on another
ContextMenu Trigger closes the previous root and opens the new one. Consumer
onContextMenu handlers still run for the new invocation and may prevent Atom's
open or reposition behavior.
disabled | boolean | false |
asChild | boolean | false |
render | RenderProp | - |
[data-slot] | "context-menu-trigger" |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-pressed] | Present only while a long press is pending |
Content
Renders the portalled menu at Trigger's pointer or keyboard anchor, manages
focus and highlight, locks scrolling when modal, and closes on outside click.
The resolved explicit, Trigger, or provider direction is preserved on the
portalled Content and nested SubContent DOM.
With a submenu open, an activation inside an ancestor menu but outside that
submenu closes only the submenu. An activation outside every menu surface
closes the complete ContextMenu tree in one activation.
side | "top" | "right" | "bottom" | "left" | "bottom" |
align | "start" | "center" | "end" | "start" |
sideOffset | number | 4 |
loop | boolean | Root loop |
ariaLabel | string | - |
onKeyDownCapture | KeyboardEventHandler | - |
role | "menu" |
aria-orientation | "vertical" |
aria-label | Value from ariaLabel |
aria-labelledby | Trigger ID when ariaLabel is absent |
[data-slot] | "menu-content" |
[data-state] | "open" | "closed" |
[data-side] | Resolved placement side |
[data-align] | Resolved placement alignment |
[data-positioned] | Present after positioning |
Group
Groups related menu entries as a div. Provide a native accessible label when
the grouping needs to be announced.
Item
Represents one command. Its unique value supports highlight and typeahead;
textValue supplies searchable text when children are not plain text.
value | string | required |
textValue | string | Text child or value |
onSelect | () => void | - |
disabled | boolean | false |
closeOnSelect | boolean | Root setting |
asChild | boolean | false |
render | RenderProp | - |
role | "menuitem" |
aria-disabled | "true" when disabled |
[data-slot] | "menu-item" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
[data-value] | Item value |
CheckboxItem
Represents an independently toggleable menu choice. It stays open by default
so several choices can be changed in one visit.
value | string | required |
textValue | string | Text child or value |
checked | boolean | "indeterminate" | false |
onCheckedChange | (checked: boolean) => void | - |
disabled | boolean | false |
closeOnSelect | boolean | false |
role | "menuitemcheckbox" |
aria-checked | Current checked state |
aria-disabled | "true" when disabled |
[data-slot] | "menu-checkbox-item" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
[data-checked] | Present when checked |
[data-value] | Item value |
RadioGroup
Shares one controlled value with nested RadioItems and renders a semantic
group.
value | string | - |
onValueChange | (value: string) => void | - |
[data-slot] | "menu-radio-group" |
RadioItem
Represents one mutually exclusive value inside RadioGroup. Selection does not
close the menu unless closeOnSelect is enabled.
value | string | required |
textValue | string | Text child or value |
disabled | boolean | false |
closeOnSelect | boolean | false |
role | "menuitemradio" |
aria-checked | Whether its value matches RadioGroup |
aria-disabled | "true" when disabled |
[data-slot] | "menu-radio-item" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
[data-checked] | Present when selected |
[data-value] | Item value |
Separator
Marks a horizontal boundary between related groups of commands.
role | "separator" |
aria-orientation | "horizontal" |
[data-slot] | "menu-separator" |
Sub
Owns controlled or uncontrolled state for one nested menu and renders no
wrapper.
open | boolean | - |
defaultOpen | boolean | false |
onOpenChange | (open: boolean) => void | - |
SubTrigger
Registers a menu item that opens SubContent after intentional mouse movement
and a hover delay, by click, or with the direction-aware submenu key. Merely
positioning a menu beneath a stationary pointer does not open the submenu.
value | string | required |
textValue | string | Text child or value |
disabled | boolean | false |
role | "menuitem" |
aria-haspopup | "menu" |
aria-expanded | Sub open state |
aria-disabled | "true" when disabled |
[data-slot] | "menu-sub-trigger" |
[data-state] | "open" | "closed" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
[data-value] | Trigger value |
SubContent
Renders a separately portalled nested menu, positions it beside SubTrigger,
and mirrors its side and open/close keys in RTL.
sideOffset | number | 4 |
loop | boolean | true |
ariaLabel | string | - |
role | "menu" |
aria-orientation | "vertical" |
aria-label | Value from ariaLabel |
aria-labelledby | SubTrigger ID when ariaLabel is absent |
[data-slot] | "menu-sub-content" |
[data-state] | "open" | "closed" |
[data-side] | Resolved placement side |
[data-positioned] | Present after positioning |
The package also exports useContextMenuContext for advanced custom parts. It
returns the current anchor point and its setter and must be used within Root.
Portal, Arrow, Label, and ItemIndicator use the shared Menu contract.
CheckboxItem supports "indeterminate"; retained DOM parts accept refs,
native props, asChild, and render; Content/SubContent expose the shared
--atom-menu-* geometry variables.
Examples
File Actions
Persistent View Choices
Accessibility
ContextMenu follows the
WAI-ARIA Menu pattern.
Trigger supports right click, keyboard invocation, and deliberate touch/pen
long press. Content moves real item focus. Keep important actions available
through a visible control as well.
Shift+F10 / ContextMenu | Opens at the keyboard anchor and highlights the first item. |
ArrowDown / ArrowUp | Moves through items, including disabled items, following loop. |
Home / End | Moves to the first or last item. |
Enter / Space | Activates the focused item unless disabled. |
| Printable character | Moves by typeahead label. |
ArrowRight | Opens a submenu in LTR; closes it in RTL. |
ArrowLeft | Closes a submenu in LTR; opens it in RTL. |
Escape | Closes the topmost submenu or menu. |
Tab / Shift+Tab | Closes and exits after/before the invoking context. |
Changelog
Unreleased
0.24.0
- Added public Agent Knowledge for component selection, required composition,
recurring mistakes, and validation.
0.20.7
- Kept the custom menu active on repeated secondary clicks, repositioned it at
the latest invocation point, and transferred it directly between registered
ContextMenu targets instead of exposing the browser menu.
- Corrected point-anchor wiring so Content positions from its pointer or
keyboard coordinate rather than falling back to the viewport corner.
- Inherited whole-tree outside dismissal when a submenu is open.
0.20.6
- Inherited movement-gated submenu hover intent so opening a parent ContextMenu
cannot also open a submenu that appears beneath a stationary pointer.
0.20.5
- Inherited resolved direction on portalled Content and SubContent so logical
row anatomy and submenu chevrons mirror with the trigger.
- Inherited block-axis submenu collision fallbacks and narrow-viewport
containment when neither inline side fits.
0.20.3
- Inherited document-only overflow locking so sticky application chrome
remains anchored while a modal ContextMenu is open.
0.12.0
- Added a cancel-safe 700 ms touch/pen long press with 10 px movement tolerance,
native-contextmenu de-duplication, temporary pressed state, and full cleanup.
- Inherited real item focus, disabled-item navigation, owner-aware Tab exit,
modal isolation, shared anatomy, mixed state, composition, and geometry.
0.3.4
- Inherited corrected modal Menu scroll-lock compensation for documents that
preserve their scrollbar gutter.
0.3.1
- Inherited reliable Menu exit-presence cleanup for closed ContextMenu and
submenu Content under global motion CSS.
0.2.0
- Fixed
Trigger so custom data-slot values override the default
context-menu-trigger slot.
- Inherited fixed submenu keyboard behavior under
Direction.Provider dir="rtl"
so ArrowLeft opens submenus, ArrowRight closes submenus, and submenu
placement mirrors to the left side.
- Fixed
Trigger so its documented asChild and render composition props
are implemented while preserving context-menu behavior.
- Fixed
Content so refs forward to the underlying shared menu content
element.
- Fixed pointer-open behavior so right-click opens without pre-highlighting the
first item; keyboard context-menu opens still seed the first highlight.
- Inherited the shared Menu typeahead behavior so a single-character search
cycles from the current matching item while multi-character buffers still
match exact prefixes.
- Added shared dismissable layer Escape handling so ContextMenu closes before
parent overlays when nested inside Dialog, Drawer, Modal, or Popover.
- Registered shared Menu content with parent modal focus scopes so ContextMenu
can remain a valid focus target inside Dialog, Drawer, and other modal
primitives.
- Added shared menu item parts to the
ContextMenu namespace object.
0.1.0