Components
Displays a menu located at the pointer, triggered by a right click or a long press.
Playground
min-w-36 wide; give it an explicit width like w-52so it doesn't resize between menus.import {
ContextMenu,
ContextMenuContent,
ContextMenuGroup,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuTrigger,
} from "@workspace/ui/components/context-menu"
<ContextMenu>
<ContextMenuTrigger className="flex aspect-video w-full max-w-xs items-center justify-center rounded-xl border border-dashed text-sm">
Right click here
</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuGroup>
<ContextMenuItem>
Back <ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>
Forward <ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Reload <ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
<ContextMenuSeparator />
<ContextMenuGroup>
<ContextMenuItem>
Save <ContextMenuShortcut>⌘S</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
Save As… <ContextMenuShortcut>⇧⌘S</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuGroup>
</ContextMenuContent>
</ContextMenu>gap-2 and size unclassed icons to size-4 automatically. Icons inherit the item color, including the accent foreground while the item is focused.<ContextMenuContent className="w-52">
<ContextMenuItem>
<CopyIcon /> Copy
</ContextMenuItem>
<ContextMenuItem>
<ScissorsIcon /> Cut
</ContextMenuItem>
<ContextMenuItem>
<ClipboardTextIcon /> Paste
</ContextMenuItem>
</ContextMenuContent>checked and onCheckedChange, and the check indicator renders at the end of the row. The menu closes on select by default — call event.preventDefault() in onSelect to keep it open while toggling several options.const [showBookmarksBar, setShowBookmarksBar] = React.useState(true)
const [showFullUrls, setShowFullUrls] = React.useState(false)
const [showDeveloperTools, setShowDeveloperTools] = React.useState(false)
<ContextMenuContent className="w-52">
<ContextMenuLabel>Appearance</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuCheckboxItem
checked={showBookmarksBar}
onCheckedChange={setShowBookmarksBar}
>
Show Bookmarks Bar
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
checked={showFullUrls}
onCheckedChange={setShowFullUrls}
disabled
>
Show Full URLs
</ContextMenuCheckboxItem>
<ContextMenuCheckboxItem
checked={showDeveloperTools}
onCheckedChange={setShowDeveloperTools}
>
Show Developer Tools
</ContextMenuCheckboxItem>
</ContextMenuContent>value and reports changes through onValueChange; each ContextMenuRadioItem marks the active choice with the same end-of-row indicator.const [person, setPerson] = React.useState("pedro")
<ContextMenuContent className="w-52">
<ContextMenuLabel>People</ContextMenuLabel>
<ContextMenuSeparator />
<ContextMenuRadioGroup value={person} onValueChange={setPerson}>
<ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem>
<ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem>
</ContextMenuRadioGroup>
</ContextMenuContent><ContextMenuContent className="w-52">
<ContextMenuItem>
Reload <ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-44">
<ContextMenuItem>Save Page…</ContextMenuItem>
<ContextMenuItem>Create Shortcut…</ContextMenuItem>
<ContextMenuItem>Name Window…</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Developer Tools</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
<ContextMenuSeparator />
<ContextMenuItem>
View Source <ContextMenuShortcut>⌥⌘U</ContextMenuShortcut>
</ContextMenuItem>
</ContextMenuContent>inset to the plain ones — it pads the start by the icon column (ps-8) so every label lines up. The prop exists on items, labels, checkbox and radio items, and sub triggers alike.<ContextMenuContent className="w-52">
<ContextMenuLabel inset>File</ContextMenuLabel>
<ContextMenuItem>
<DownloadSimpleIcon /> Download
</ContextMenuItem>
<ContextMenuItem inset>Rename</ContextMenuItem>
<ContextMenuItem inset>Duplicate</ContextMenuItem>
</ContextMenuContent>variant="destructive" paints the item — icon included — with the destructive token and swaps the focus highlight for a translucent destructive wash. Reserve it for the irreversible row and keep a separator before it.<ContextMenuContent className="w-52">
<ContextMenuItem>
<PencilSimpleIcon /> Edit
</ContextMenuItem>
<ContextMenuItem>
<ShareNetworkIcon /> Share
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem variant="destructive">
<TrashIcon /> Delete
</ContextMenuItem>
</ContextMenuContent>dir="rtl" on the ContextMenu root. Items, indicators and shortcuts are laid out with logical properties (ps-8, pe-8, end-2, ms-auto), and the submenu caret flips via rtl:rotate-180 — so the whole menu mirrors without extra classes.<ContextMenu dir="rtl">
<ContextMenuTrigger className="…">انقر بزر الفأرة الأيمن هنا</ContextMenuTrigger>
<ContextMenuContent className="w-52">
<ContextMenuItem>
رجوع <ContextMenuShortcut>⌘[</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem disabled>
تقدم <ContextMenuShortcut>⌘]</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuItem>
إعادة تحميل <ContextMenuShortcut>⌘R</ContextMenuShortcut>
</ContextMenuItem>
<ContextMenuSub>
<ContextMenuSubTrigger>المزيد من الأدوات</ContextMenuSubTrigger>
<ContextMenuSubContent className="w-44">
<ContextMenuItem>حفظ الصفحة…</ContextMenuItem>
<ContextMenuItem>أدوات المطور</ContextMenuItem>
</ContextMenuSubContent>
</ContextMenuSub>
</ContextMenuContent>
</ContextMenu>aria-checked; disabled items are skipped. But the menu only opens from a right click or a long press — there is no keyboard gesture and no visible affordance — so label the trigger surface with what it does and keep every action reachable elsewhere too (a toolbar or a Dropdown Menu). Use pointer variants to match the hint to the input device.<ContextMenuTrigger className="…"> <span className="hidden pointer-fine:inline-block">Right click here</span> <span className="hidden pointer-coarse:inline-block">Long press here</span> </ContextMenuTrigger>
| Component | Prop | Default |
|---|---|---|
| ContextMenu | onOpenChange?: (open: boolean) => void | — |
| ContextMenu | modal?: boolean | true |
| ContextMenu | dir?: "ltr" | "rtl" | "ltr" |
| ContextMenuTrigger | disabled?: boolean | false |
| ContextMenuContent | alignOffset?: number | 0 |
| ContextMenuContent | avoidCollisions?: boolean | true |
| ContextMenuItem | variant?: "default" | "destructive" | "default" |
| ContextMenuItem | inset?: boolean, disabled?: boolean, onSelect?: (event: Event) => void | — |
| ContextMenuCheckboxItem | checked?: boolean, onCheckedChange?: (checked: boolean) => void | — |
| ContextMenuRadioGroup | value?: string, onValueChange?: (value: string) => void | — |
<ContextMenu data-slot="context-menu" /> <ContextMenuTrigger data-slot="context-menu-trigger" data-state /> <ContextMenuContent data-slot="context-menu-content" data-side data-align /> <ContextMenuGroup data-slot="context-menu-group" /> <ContextMenuLabel data-slot="context-menu-label" data-inset /> <ContextMenuItem data-slot="context-menu-item" data-variant data-inset /> <ContextMenuCheckboxItem data-slot="context-menu-checkbox-item" data-state /> <ContextMenuRadioGroup data-slot="context-menu-radio-group" /> <ContextMenuRadioItem data-slot="context-menu-radio-item" data-state /> <ContextMenuSeparator data-slot="context-menu-separator" /> <ContextMenuShortcut data-slot="context-menu-shortcut" /> <ContextMenuSub data-slot="context-menu-sub" /> <ContextMenuSubTrigger data-slot="context-menu-sub-trigger" data-open /> <ContextMenuSubContent data-slot="context-menu-sub-content" />
Tokens used
13 design tokens consumed by the Context Menu component.
--popover
Popover
--popover-foreground
Popover Foreground
--accent
Accent
--accent-foreground
Accent Foreground
--destructive
Destructive
--muted-foreground
Muted Foreground
--foreground
Foreground
--border
Border
--radius-md
Radius 0.8× (8px)
--radius-sm
Radius 0.6× (6px)
--text-sm
Text Small (14px)
--text-xs
Text Extra Small (12px)
--spacing
Spacing unit (4px)