Context Menu
A menu of actions revealed by right-clicking (or long-pressing) a target — items, submenus, separators, labels, shortcuts, and checkbox/radio selections.
- Status
- Since
0.1.0- Accessibility pattern
- APG menu
Last updated
Install
Add Context Menu from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/context-menuThe same command installs the registry items it composes: @vegastack/floating-surface.
Usage
import {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
} from "@/components/ui/context-menu";
<ContextMenu>
<ContextMenuTrigger>Right-click here</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Copy</ContextMenuItem>
<ContextMenuItem tone="destructive">Delete</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>;Anatomy
ContextMenu is a compound component built on Base UI's ContextMenu. Unlike DropdownMenu, the
trigger is a right-click target area (not a button); the content portals to <body> and positions
itself against the pointer where the menu was opened.
<ContextMenu>
<ContextMenuTrigger />
<ContextMenuContent>
<ContextMenuGroup>
<ContextMenuLabel />
<ContextMenuItem />
<ContextMenuShortcut />
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuCheckboxItem />
<ContextMenuRadioGroup>
<ContextMenuRadioItem />
</ContextMenuRadioGroup>
<ContextMenuSub>
<ContextMenuSubTrigger />
<ContextMenuSubContent>
<ContextMenuItem />
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>ContextMenu— the root that groups every part (ContextMenu.Root; renders no element).ContextMenuTrigger— the area you right-click, long-press, or focus and open with Menu / Shift+F10 (data-slot="context-menu-trigger"; renders a<div>). Userenderto compose with your own element.ContextMenuContent— the floating popup; portals to<body>and positions against the pointer (data-slot="context-menu-content"). Acceptsside,align,sideOffset,collisionPadding, and exposesportalProps/positionerProps.ContextMenuItem— a selectable action (data-slot="context-menu-item"). Supportstone="destructive"andinset.ContextMenuGroup+ContextMenuLabel— a labelled section; the label becomes the group's accessible name.ContextMenuSeparator— a divider between sections (role="separator").ContextMenuShortcut— an inline-end-aligned keyboard hint inside an item.ContextMenuCheckboxItem— a togglable item with a check indicator (checked/onCheckedChange).ContextMenuRadioGroup+ContextMenuRadioItem— single-select items (value/onValueChange).ContextMenuSub+ContextMenuSubTrigger+ContextMenuSubContent— a nested submenu.
Examples
A rich menu with grouped items, labels, keyboard shortcuts, checkbox + radio selections, a disabled item, a destructive action, and a nested submenu.
Items
Items take leading lucide-react icons as children, a trailing ContextMenuShortcut, and a
tone="destructive" for delete/remove actions. Use inset to align items that have no leading
icon with those that do.
<ContextMenuItem>
<Copy />
Copy
<ContextMenuShortcut>⌘C</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem tone="destructive">
<Trash2 />
Delete
</ContextMenuItem>Inset items
Set inset on ContextMenuItem, ContextMenuLabel, and ContextMenuSubTrigger to add left
padding so icon-less rows line up with the indicator column of checkbox / radio items.
<ContextMenuGroup>
<ContextMenuLabel inset>Layout</ContextMenuLabel>
<ContextMenuItem inset>Back</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSubTrigger inset>More tools</ContextMenuSubTrigger>Checkbox & radio items
ContextMenuCheckboxItem toggles a setting and stays open on click; ContextMenuRadioGroup
wraps ContextMenuRadioItems for single-select.
<ContextMenuCheckboxItem checked={showGrid} onCheckedChange={setShowGrid}>
Show grid
</ContextMenuCheckboxItem>
<ContextMenuRadioGroup value={role} onValueChange={setRole}>
<ContextMenuRadioItem value="admin">Admin</ContextMenuRadioItem>
<ContextMenuRadioItem value="member">Member</ContextMenuRadioItem>
</ContextMenuRadioGroup>Submenus
Nest a ContextMenuSub to reveal a second-level popup. The trigger shows a trailing chevron and
opens on hover or with the → arrow key.
<ContextMenuSub>
<ContextMenuSubTrigger>Invite users</ContextMenuSubTrigger>
<ContextMenuSubContent>
<ContextMenuItem>Email</ContextMenuItem>
<ContextMenuItem>Message</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>API Reference
ContextMenu
ContextMenu adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuTrigger
ContextMenuTrigger adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
Data attributes and CSS variables on ContextMenuTrigger
| Attribute | Values |
|---|---|
data-slot | "context-menu-trigger" |
ContextMenuGroup
ContextMenuGroup adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuSub
ContextMenuSub adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuRadioGroup
ContextMenuRadioGroup adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | 'start' | Alignment relative to the anchor along the chosen side. |
collisionPadding | Padding | 8 | Padding from the collision boundary so the popup never touches the viewport edge. |
portalProps | Omit<MenuPortalProps, "children"> | — | Props forwarded to the underlying Base UI Portal. |
positionerProps | Omit<MenuPositionerProps, "align" | "children" | "collisionPadding" | "side" | "sideOffset"> | — | Props forwarded to the underlying Base UI Positioner. |
side | Side | 'bottom' | Which side of the anchor to render against. May flip to avoid collisions. |
sideOffset | number | OffsetFunction | 4 | Distance in pixels between the anchor and the popup. |
ContextMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Adds inline-start padding so the label aligns with rows that carry a leading indicator. |
tone | MenuItemTone | "default" | destructive tints the row for delete/remove actions. |
ContextMenuCheckboxItem
ContextMenuCheckboxItem adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuRadioItem
ContextMenuRadioItem adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuLabel
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Indents the label to line up with inset rows. |
ContextMenuSeparator
ContextMenuSeparator adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuShortcut
ContextMenuShortcut adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
ContextMenuSubTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Indents the trigger to line up with inset rows. |
ContextMenuSubContent
| Prop | Type | Default | Description |
|---|---|---|---|
align | Align | 'start' | Alignment relative to the anchor along the chosen side. |
collisionPadding | Padding | 8 | Padding from the collision boundary so the popup never touches the viewport edge. |
portalProps | Omit<MenuPortalProps, "children"> | — | Props forwarded to the underlying Base UI Portal. |
positionerProps | Omit<MenuPositionerProps, "align" | "children" | "collisionPadding" | "side" | "sideOffset"> | — | Props forwarded to the underlying Base UI Positioner. |
side | Side | 'bottom' | Which side of the anchor to render against. May flip to avoid collisions. |
sideOffset | number | OffsetFunction | 4 | Distance in pixels between the anchor and the popup. |
Accessibility
- The popup renders with
role="menu"; items aremenuitem,menuitemcheckbox, andmenuitemradio. AContextMenuLabelinside a group becomes that group's accessible name. - Open with a right-click or a long-press; the keyboard Menu/Shift+F10 shortcut also opens the menu when the trigger is focused.
- Full keyboard support via Base UI's roving focus; the highlighted item uses
data-highlighted(bg-accent text-accent-foreground) — there is nooutline: nonewithout a visible focus state. - The menu is modal: page scroll locks and focus is trapped while open, then returns to the trigger on close.
- Icons inside items are decorative; the item's text carries the meaning.
| Key | Action |
|---|---|
| Right-click / long-press / Shift+F10 | Open the menu on the target. |
| ↑ / ↓ | Move between items (wraps at the ends). |
| → | Open the focused submenu. |
| ← / Esc | Close the current submenu (or the whole menu). |
| Enter / Space | Activate the focused item. |
| A–Z | Typeahead — jump to the next item matching the typed letters. |
| Contract | States tested |
|---|---|
| Behaviour | default, active, checked, disabled, error, open, selected |
| Accessibility | keyboard, labeled |
| Visual | default, checked, error |