Context menu
A menu of actions on right click or long press, with submenus, checkbox and radio items, and hold feedback on touch.
Used in Pro blocks: Chat Sidebar
Last action: Nothing yet
pnpm dlx shadcn@latest add https://hextaui.com/r/context-menu.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cnCopy and paste the following code into your project.
components/ui/context-menu.tsx Update the import paths to match your project setup.
Give every row its own menu. The open row keeps a highlight, and a destructive item hands off to an alert dialog for confirmation.
Pass open and onOpenChange to own the state. The second argument says why it changed, such as trigger-press, outside-press or escape-key. Items with closeOnClick={false} keep it open.
A disabled <ContextMenu /> gives the area back to the browser’s own menu. Disabled items stay visible but are skipped by the keyboard.
On touch screens the menu opens after a long press. While the finger is held, the area shrinks slightly so people know the press registered. Moving the finger cancels it. Set holdFeedback={false} to turn it off.
Long labels wrap inside a 20rem maximum width, and tall menus scroll within the space left in the viewport.
Submenus open on hover or with the arrow keys, at any depth. A disabled submenu trigger never opens.
The menu layers above other overlays, and Escape closes only the menu, not the sheet behind it.
Use render to make the trigger any element, such as a figure, or to turn an item into a link.
The menu reads the trigger’s direction, so submenus open to the left and arrow keys flip.
| Key | Action |
|---|---|
| ↓ | Highlights the next item, wrapping at the end. |
| ↑ | Highlights the previous item, wrapping at the start. |
| Home | Highlights the first item. |
| End | Highlights the last item. |
| EnterSpace | Runs the highlighted item. Checkbox and radio items toggle and keep the menu open. |
| → | Opens the highlighted submenu and moves into it. ← in right-to-left layouts. |
| ← | Closes the current submenu and returns to its trigger. → in right-to-left layouts. |
| Esc | Closes the current menu. In a submenu, only that submenu closes. |
| A–Z | Highlights the next item starting with that letter. |
- A context menu is a shortcut. Make every action in it reachable some other way too, such as a visible button or a dropdown menu, since many people never right click or long press.
- Browsers also fire the context menu event for Shift F10 and the Menu key on a focused element, so a focusable element inside the trigger lets keyboard users open it.
- Shortcuts in
<ContextMenuShortcut />are labels only. Bind the keys yourself. - With reduced motion on, the hold feedback and the item blink are skipped and the menu only fades.
Built on the Base UI context menu. Every part accepts the props of the primitive it wraps, and a function className that receives the part’s state.
| Prop | Type | Default |
|---|---|---|
open | boolean | – |
defaultOpen | boolean | false |
onOpenChangedetails.reason says what caused the change. | (open: boolean, details) => void | – |
onOpenChangeCompleteRuns after the open or close animation ends. | (open: boolean) => void | – |
disabledShows the browser’s native menu instead. | boolean | false |
loopFocusWrap arrow-key navigation at the ends. | boolean | true |
highlightItemOnHover | boolean | true |
actionsRefClose the menu imperatively. | RefObject<{ close, unmount }> | – |
| Prop | Type | Default |
|---|---|---|
holdFeedbackShrink the area slightly while a long press is held on touch screens. | boolean | true |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-trigger" | Target the trigger in CSS. |
data-popup-open | Present while its menu is open. |
data-holding | Present while a long press is held. |
data-pressed | Present while the trigger is pressed. |
| Prop | Type | Default |
|---|---|---|
sidePreferred side, relative to the pointer. | "top" | "right" | "bottom" | "left" | "inline-start" | "inline-end" | "bottom" |
align | "start" | "center" | "end" | "start" |
sideOffset | number | 0 |
alignOffset | number | 0 |
collisionPaddingSpace to keep between the menu and the viewport edges. | number | { top, right, bottom, left } | – |
collisionAvoidanceHow the menu flips or shifts when it would overflow. | CollisionAvoidance | – |
anchorPosition against something other than the pointer. | Element | VirtualElement | RefObject | – |
finalFocusWhere focus goes after the menu closes. | boolean | RefObject | (closeType) => HTMLElement | boolean | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-content" | The menu popup. |
data-open | Present while open. |
data-starting-style | Present while the menu animates in. |
data-ending-style | Present while the menu animates out. |
data-side | The side it was placed on after collisions. |
data-chosen | Present after an item is clicked. The fade-out waits for the blink. |
--transform-origin | The point the scale animation grows from. |
--available-height | Space left in the viewport. Caps the menu height. |
| Prop | Type | Default |
|---|---|---|
variant | "default" | "destructive" | "default" |
insetIndent the label to line up with checkbox items. | boolean | false |
onClickRuns on click, Enter or Space. The menu closes after a short blink. | (event) => void | – |
closeOnClick | boolean | true |
disabled | boolean | false |
labelText used for typeahead when children are not plain text. | string | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-item" | Target items in CSS. |
data-variant | The current variant. |
data-highlighted | Present on the item under the pointer or keyboard focus. |
data-disabled | Present when the item is disabled. |
data-inset | Present when inset is set. |
data-chosen | Present on the clicked item while it blinks. |
| Prop | Type | Default |
|---|---|---|
checked | boolean | – |
defaultChecked | boolean | false |
onCheckedChange | (checked: boolean, details) => void | – |
closeOnClick | boolean | false |
inset | boolean | false |
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-checkbox-item" | Target checkbox items in CSS. |
data-checked | Present when checked. |
data-unchecked | Present when unchecked. |
data-highlighted | Present on the item under the pointer or keyboard focus. |
data-disabled | Present when the item is disabled. |
data-inset | Present when inset is set. |
data-chosen | Present on the clicked item while it blinks. |
| Prop | Type | Default |
|---|---|---|
value | any | – |
defaultValue | any | – |
onValueChange | (value: any, details) => void | – |
disabled | boolean | false |
| Prop | Type | Default |
|---|---|---|
value | any | – |
closeOnClick | boolean | false |
inset | boolean | false |
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-radio-item" | Target radio items in CSS. |
data-checked | Present when selected. |
data-highlighted | Present on the item under the pointer or keyboard focus. |
data-disabled | Present when the item is disabled. |
data-inset | Present when inset is set. |
data-chosen | Present on the clicked item while it blinks. |
| Prop | Type | Default |
|---|---|---|
inset | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
Inside <ContextMenuGroup /> or <ContextMenuRadioGroup /> it labels the group for assistive tech. Elsewhere it is a plain heading.
| Prop | Type | Default |
|---|---|---|
open | boolean | – |
defaultOpen | boolean | false |
onOpenChange | (open: boolean, details) => void | – |
disabled | boolean | false |
closeParentOnEscClose the whole menu on Escape, not just this submenu. | boolean | false |
| Prop | Type | Default |
|---|---|---|
inset | boolean | false |
openOnHover | boolean | true |
delayMilliseconds of hover before the submenu opens. | number | 100 |
closeDelay | number | 0 |
disabled | boolean | false |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-sub-trigger" | Target submenu triggers in CSS. |
data-popup-open | Present while its submenu is open. |
data-highlighted | Present while highlighted. |
data-disabled | Present when disabled. |
| Prop | Type | Default |
|---|---|---|
sideOpens toward the inline end by default. | "top" | "right" | "bottom" | "left" | "inline-start" | "inline-end" | – |
align | "start" | "center" | "end" | – |
sideOffset | number | 0 |
alignOffset | number | -4 |
collisionPaddingSpace to keep between the menu and the viewport edges. | number | { top, right, bottom, left } | – |
collisionAvoidanceHow the menu flips or shifts when it would overflow. | CollisionAvoidance | – |
anchorPosition against something other than the pointer. | Element | VirtualElement | RefObject | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="context-menu-sub-content" | The submenu popup. Takes the same state attributes as the menu. |
| Prop | Type | Default |
|---|---|---|
dirKeeps shortcuts like ⇧⌘S in order inside right-to-left menus. | "ltr" | "rtl" | "ltr" |
| Attribute | Description |
|---|---|
data-slot="context-menu-shortcut" | The shortcut label. |
<ContextMenuGroup /> groups related items under a label. <ContextMenuSeparator /> draws a divider. Both accept render and className, and carry context-menu-group and context-menu-separator as their data-slot.
- Alert dialogA confirmation dialog for destructive or important actions that waits for async work, and becomes a bottom sheet on phones.
- CommandA searchable list of actions, inline or as a ⌘K palette, with pages, shortcuts and highlighted matches.
- DialogA window over the page for forms and focused tasks, with a pinned header and footer, nesting, and a swipeable bottom sheet on phones.
- DrawerA panel that slides in from any edge and follows your finger, with snap points, a working handle and nested drawers that stack.
- Dropdown menuA menu of actions and options behind a button, with groups, submenus, checkbox and radio items, and shortcuts.
- Hover cardA preview card that opens when a link is hovered or focused, for content sighted users can glance at.
Used in Pro blocks
Blocks that build on Context menu.
Every block comes with HextaUI Pro, and Prompt Input is free.