Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Context Menu for React and Tailwind

A menu shown on right-click.

Installation

bunx --bun shadcn@latest add https://kit.dev/r/context-menu.json

Anatomy

ContextMenu
├── ContextMenuTrigger
└── ContextMenuContent
    ├── ContextMenuGroup
    │   └── ContextMenuGroupLabel
    ├── ContextMenuItem
    ├── ContextMenuQuickItem
    ├── ContextMenuCheckboxItem
    ├── ContextMenuRadioGroup
    │   └── ContextMenuRadioItem
    ├── ContextMenuSub
    │   ├── ContextMenuSubTrigger
    │   └── ContextMenuSubContent
    ├── ContextMenuSeparator
    ├── ContextMenuShortcut
    └── ContextMenuArrow

ContextMenu is Menu with ContextMenuTrigger (right-click / long-press) instead of MenuTrigger (click). Content, items, groups, submenus, checkboxes, and radios are the same Menu parts. useContextMenu is the machine hook for ContextMenuRootProvider and returns { api, service }. useContextMenuContext / ContextMenuContext is in-tree (the api).

ContextMenuContent portals the list and includes the positioner. The trigger sets anchorPoint from the pointer.

Usage

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuGroup,
  ContextMenuItem,
  ContextMenuTrigger,
} from "@/components/ui/context-menu";
<ContextMenu>
  <ContextMenuTrigger>Right click here</ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuGroup>
      <ContextMenuItem value="action-1">Action 1</ContextMenuItem>
      <ContextMenuItem value="action-2">Action 2</ContextMenuItem>
      <ContextMenuItem value="action-3">Action 3</ContextMenuItem>
    </ContextMenuGroup>
  </ContextMenuContent>
</ContextMenu>

shadcn.io Menu defaults lazyMount and unmountOnExit to true (Ark: false). Context Menu defaults positioning.placement to "bottom-start" (Menu: "bottom-end"). Give every item a stable value.

Controlled

Control open state with open and onOpenChange. { open: boolean }.

Root Provider

Use useContextMenu with ContextMenuRootProvider when you need the API outside the tree. Pass machine options (positioning, closeOnSelect, onSelect, …) to useContextMenu(), not to the provider. The hook returns { api, service } — call menu.api.setOpen(true) and pass the whole return value to the provider.

Examples

Nested menu

Use ContextMenuSub, ContextMenuSubTrigger, and ContextMenuSubContent for nested submenus.

Icons

Combine icons with labels for quick scanning. Decorative icons should be aria-hidden="true".

Shortcuts

Use ContextMenuShortcut to show keyboard hints next to items.

Checkboxes

Use ContextMenuCheckboxItem for toggles. Control with checked and onCheckedChange.

Radio group

Use ContextMenuRadioGroup and ContextMenuRadioItem for a single choice. onValueChange receives { value: string }.

Use asChild on ContextMenuItem with an <a> or Next.js Link.

With group label

Use ContextMenuGroup with heading or ContextMenuGroupLabel.

With separator

Use ContextMenuSeparator between related groups.

Quick items

Use ContextMenuQuickItem for icon-above-text actions in a horizontal row.

Scrollable

Add max-h-* and overflow (already overflow-y-auto on content) to cap height.

Open a dialog

Open a Dialog from onSelect on an item.

Destructive

Use variant="destructive" on ContextMenuItem for irreversible actions.

Context

Read open and highlighted state with ContextMenuContext or useContextMenuContext.

Guides

Context Menu vs Menu

Context MenuMenu
TriggerRight-click / long-press (ContextMenuTrigger)Click (MenuTrigger)
AnchorPointer (anchorPoint)Trigger element
Placement defaultbottom-startbottom-end
ContentSame Menu partsSame Menu parts

Use Menu for a button-triggered dropdown. Use Command when the list is searchable.

Pointer position

ContextMenuTrigger sets anchorPoint from the pointer. positioning.placement is relative to that point. Override with positioning on ContextMenu (or on useContextMenu()).

<ContextMenu positioning={{ placement: "right-start", gutter: 8 }}>

Custom trigger surface

ContextMenuTrigger renders a button by default. Merge onto a card, image, or other host with asChild and a single child.

<ContextMenuTrigger asChild>
  <div className="rounded-xl border p-6">Right click the card</div>
</ContextMenuTrigger>

The host should be focusable if keyboard users need the same actions (Shift+F10 / context-menu key). Offer a visible Menu on small viewports where long-press is not obvious.

Item values

Every ContextMenuItem, checkbox, radio, and submenu item needs a unique value. Typeahead and highlight tracking use it.

API Reference

shadcn.io Context Menu wraps Menu (Ark Menu machine). Defaults below are shadcn.io Context Menu values. lazyMount / unmountOnExit are true (Ark: false). positioning.placement is "bottom-start" (Menu: "bottom-end"). data-scope stays menu; shadcn.io sets data-slot="context-menu" on the root.

asChild merges props onto a single child element.

ContextMenu

Root. Same as Menu besides the placement default. Renders no extra chrome; content is created by ContextMenuContent.

PropTypeDefaultDescription
anchorPointPoint | null-Positioning point. Set by the context trigger from the pointer.
aria-labelstring-Accessibility label for the menu.
asChildbooleanfalseRender the child element instead of the root.
classNamestring-Class names on the root.
closeOnSelectbooleantrueClose the menu when an item is selected.
compositebooleantrueTreat as composed with other composite widgets.
defaultHighlightedValuestring | null-Uncontrolled initial highlighted value.
defaultOpenboolean-Uncontrolled initial open state.
defaultTriggerValuestring | null-Uncontrolled initial trigger value.
hideMode"display-none" | "activity""display-none"How to hide mounted-but-closed content. activity needs React 19+.
highlightedValuestring | null-Controlled highlighted value.
idstring-Unique id for the machine.
idsPartial<{ trigger: string | ((value?: string) => string); contextTrigger: string | ((value?: string) => string); content: string; groupLabel: (id: string) => string; group: (id: string) => string; positioner: string; arrow: string }>-Element ids for composition.
immediateboolean-Apply presence changes immediately instead of the next frame.
lazyMountbooleantrueMount content on first open.
loopFocusbooleanfalseLoop keyboard focus through items.
navigate(details: NavigateDetails) => void-Called when a link item is chosen. { value, node, href }.
onExitComplete() => void-Called when the close animation finishes.
onFocusOutside(event: FocusOutsideEvent) => void-Called when focus moves outside.
onHighlightChange(details: HighlightChangeDetails) => void-Called when the highlighted item changes. { highlightedValue }.
onInteractOutside(event: InteractOutsideEvent) => void-Called on outside interaction.
onOpenChange(details: OpenChangeDetails) => void-Called when open state changes. { open }.
onPointerDownOutside(event: PointerDownOutsideEvent) => void-Called on pointer down outside.
onSelect(details: SelectionDetails) => void-Called when an item is selected. { value }.
onTriggerValueChange(details: TriggerValueChangeDetails) => void-Called when the active trigger changes. { value, triggerElement }.
openboolean-Controlled open state.
positioningPositioningOptions{ placement: "bottom-start" }Floating position relative to anchorPoint.
presentboolean-Controlled presence.
skipAnimationOnMountbooleanfalseSkip the initial presence animation.
triggerValuestring | null-Controlled trigger value.
typeaheadbooleantrueJump to items by typing printable characters.
unmountOnExitbooleantrueUnmount content after the close animation.
AttributeDescription
data-slotcontext-menu
data-scopemenu
data-partroot

ContextMenuTrigger

Opens the menu on right-click or long-press. Renders a button.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the trigger.
valuestring-Id for this trigger when several share one menu.
AttributeDescription
data-slotcontext-menu-trigger
data-scopemenu
data-partcontext-trigger
data-state"open" or "closed"
data-valueThe trigger value

ContextMenuContent

Portaled list surface. Includes the positioner. Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the content.
AttributeDescription
data-slotcontext-menu-content
data-scopemenu
data-partcontent
data-state"open" or "closed"
data-placementPlacement of the content
data-nestedPresent when nested in another menu
data-has-nestedPresent when this menu has nested menus
CSS variableDescription
--available-widthAvailable width in the viewport (on the positioner)
--available-heightAvailable height in the viewport (on the positioner)
--reference-widthWidth of the trigger (on the positioner)
--reference-heightHeight of the trigger (on the positioner)
--transform-originTransform origin for open/close animation
--layer-indexIndex in the dismissable layer stack
--nested-layer-countNumber of nested menus

ContextMenuItem

A single action. Renders a div. value is required.

PropTypeDefaultDescription
valuestringrequiredStable id. Used for highlight and typeahead.
valueTextstring-Text used for typeahead when it differs from the children.
variant"default" | "destructive""default"Visual variant.
disabledboolean-Disable the item.
closeOnSelectboolean-Override root closeOnSelect for this item.
onSelect() => void-Called when this item is chosen.
asChildbooleanfalseMerge onto a single child (for example an <a>).
classNamestring-Class names on the item.
AttributeDescription
data-slotcontext-menu-item
data-scopemenu
data-partitem
data-highlightedPresent when highlighted
data-disabledPresent when disabled
data-valueThe item value
data-variant"default" or "destructive"

ContextMenuCheckboxItem

Toggle item. Indicator is built in.

PropTypeDefaultDescription
valuestringrequiredStable id.
checkedboolean-Controlled checked state.
onCheckedChange(checked: boolean) => void-Called when checked state changes.
disabledboolean-Disable the item.
valueTextstring-Text used for typeahead.
closeOnSelectboolean-Override root closeOnSelect.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the item.
AttributeDescription
data-slotcontext-menu-checkbox-item
data-scopemenu
data-partitem
data-state"checked" or "unchecked"
data-highlightedPresent when highlighted
data-disabledPresent when disabled

ContextMenuRadioGroup

Mutually exclusive options. Pass heading to render ContextMenuGroupLabel.

PropTypeDefaultDescription
valuestring-Controlled selected value.
onValueChange(details: { value: string }) => void-Called when the selection changes.
headingstring-Group label.
idstring-Group id.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the group.
AttributeDescription
data-slotcontext-menu-radio-group
data-scopemenu
data-partitem-group

ContextMenuRadioItem

A single option in a radio group. Indicator is built in.

PropTypeDefaultDescription
valuestringrequiredOption value.
disabledboolean-Disable the item.
valueTextstring-Text used for typeahead.
closeOnSelectboolean-Override root closeOnSelect.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the item.
AttributeDescription
data-slotcontext-menu-radio-item
data-scopemenu
data-partitem
data-state"checked" or "unchecked"
data-highlightedPresent when highlighted
data-disabledPresent when disabled

ContextMenuGroup

Groups related items. Pass heading to render ContextMenuGroupLabel.

PropTypeDefaultDescription
headingstring-Group label.
idstring-Group id.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the group.
AttributeDescription
data-slotcontext-menu-group
data-scopemenu
data-partitem-group

ContextMenuGroupLabel

Label for a group. Usually created by heading on ContextMenuGroup. Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the label.
AttributeDescription
data-slotcontext-menu-group-label
data-scopemenu
data-partitem-group-label

ContextMenuSeparator

Visual divider. Renders a hr.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the separator.
AttributeDescription
data-slotcontext-menu-separator
data-scopemenu
data-partseparator

ContextMenuQuickItem

Item with icon-above-text layout. Same props as ContextMenuItem. Use in a horizontal flex row.

PropTypeDefaultDescription
valuestringrequiredStable id.
variant"default" | "destructive""default"Visual variant.
disabledboolean-Disable the item.
closeOnSelectboolean-Override root closeOnSelect.
onSelect() => void-Called when this item is chosen.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the item.
AttributeDescription
data-slotcontext-menu-quick-item
data-scopemenu
data-partitem

ContextMenuSub / ContextMenuSubTrigger / ContextMenuSubContent

Nested menu. Wrap ContextMenuSubTrigger and ContextMenuSubContent in ContextMenuSub. ContextMenuSub is another Menu root (data-slot="context-menu-sub"). ContextMenuSubContent is portaled.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the part.

ContextMenuSubTrigger renders a trigger item with a chevron. ContextMenuSub accepts the same root props as ContextMenu.

AttributeDescription
data-slotcontext-menu-sub, context-menu-sub-trigger, context-menu-sub-content
data-scopemenu
data-partroot, trigger-item, content
data-state"open" or "closed" (trigger and content)
data-highlightedPresent when the subtrigger is highlighted

ContextMenuShortcut

Keyboard hint aligned to the end of an item. Renders a span.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the shortcut.
AttributeDescription
data-slotcontext-menu-shortcut

ContextMenuArrow

Optional arrow toward the anchor. Less common for pointer menus.

PropTypeDefaultDescription
classNamestring-Class names on the arrow.
asChildbooleanfalseMerge onto a single child.
CSS variableDefault
--arrow-backgroundvar(--popover)
--arrow-sizecalc(1.5 * var(--spacing))
AttributeDescription
data-slotcontext-menu-arrow
data-scopemenu
data-partarrow

ContextMenuRootProvider

Root alternative that takes { api, service } from useContextMenu(). Presence (lazyMount, unmountOnExit) can still be set on the provider.

PropTypeDefaultDescription
valueUseMenuReturnrequiredReturn value of useContextMenu().
hideMode"display-none" | "activity""display-none"How to hide mounted-but-closed content.
immediateboolean-Apply presence changes immediately.
lazyMountbooleantrueMount content on first open.
onExitComplete() => void-Called when the close animation finishes.
presentboolean-Controlled presence.
skipAnimationOnMountbooleanfalseSkip the initial presence animation.
unmountOnExitbooleantrueUnmount after the close animation.

Pass positioning, closeOnSelect, and other machine options to useContextMenu(), not to ContextMenuRootProvider. To match ContextMenu, pass positioning: { placement: "bottom-start" }.

AttributeDescription
data-slotcontext-menu
data-scopemenu
data-partroot

useContextMenu

Creates the menu API for ContextMenuRootProvider. Returns { api, service } — not the api alone.

const menu = useContextMenu({
  positioning: { placement: "bottom-start" },
});

menu.api.setOpen(true);

ContextMenuContext / useContextMenuContext

Render-prop or hook access to menu state (the api). Use inside ContextMenu or ContextMenuRootProvider.

PropertyTypeDescription
openbooleanWhether the menu is open.
setOpen(open: boolean) => voidOpen or close the menu.
triggerValuestring | nullActive trigger value.
setTriggerValue(value: string | null) => voidSet the trigger value.
highlightedValuestring | nullHighlighted item value.
setHighlightedValue(value: string) => voidHighlight an item by value.
reposition(options?: Partial<PositioningOptions>) => voidUpdate menu position.
getItemState(props: { value: string; disabled?: boolean }) => ItemStateState for one item (id, disabled, highlighted).
getOptionItemState(props: OptionItemProps) => OptionItemStateState for a checkbox or radio item (includes checked).

ContextMenuContext children: (context) => ReactNode.

ContextMenuItemContext / useContextMenuItemContext

Render-prop or hook access to one item. Use inside an item.

PropertyTypeDescription
idstringItem id.
disabledbooleanWhether the item is disabled.
highlightedbooleanWhether the item is highlighted.
checkedbooleanWhether a checkbox/radio item is checked (optional).

ContextMenuItemContext children: (context) => ReactNode.

Accessibility

Complies with the Menu WAI-ARIA design pattern. The trigger should be reachable from the keyboard when the same actions are required without a pointer. Label icon-only items. Keep destructive actions on variant="destructive" and confirm them in a Dialog when needed.

Keyboard support

KeyDescription
Shift + F10Opens the menu when the trigger is focused.
Context MenuOpens the menu when the trigger is focused (when the keyboard has this key).
ArrowDownMoves to the next item. Opens the menu when used on the trigger.
ArrowUpMoves to the previous item.
HomeMoves to the first item.
EndMoves to the last item.
Enter / SpaceSelects the highlighted item.
ArrowRightOpens a submenu (LTR).
ArrowLeftCloses a submenu (LTR).
EscapeCloses the menu.
Printable charactersTypeahead: jumps to a matching item when typeahead is on.