Put relevant actions at the pointer. Right-click or long press the surface to open the menu, then click outside or press Escape to close it.
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem>Profile</ContextMenuItem> <ContextMenuItem>Billing</ContextMenuItem> <ContextMenuItem>Team</ContextMenuItem> <ContextMenuItem>Subscription</ContextMenuItem> </ContextMenuContent> </ContextMenu> )}Installation
npx @arctis-sh/@arctis-sh/ui@latest add context-menuUsage
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,} from "@/components/ui/context-menu"<ContextMenu> <ContextMenuTrigger>Right-click here</ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem>Open</ContextMenuItem> <ContextMenuItem>Rename</ContextMenuItem> <ContextMenuItem>Duplicate</ContextMenuItem> <ContextMenuItem>Move to…</ContextMenuItem> </ContextMenuContent></ContextMenu>Composition
ContextMenu
├── ContextMenuTrigger
└── ContextMenuContent
├── ContextMenuGroup
│ ├── ContextMenuLabel
│ ├── ContextMenuItem
│ │ └── ContextMenuShortcut
│ └── ContextMenuItem
├── ContextMenuSeparator
├── ContextMenuCheckboxItem
├── ContextMenuRadioGroup
│ └── ContextMenuRadioItem
└── ContextMenuSub
├── ContextMenuSubTrigger
└── ContextMenuSubContent
└── ContextMenuItemBasic
A straightforward menu with a few related actions. Choosing one closes the menu.
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem>Profile</ContextMenuItem> <ContextMenuItem>Billing</ContextMenuItem> <ContextMenuItem>Team</ContextMenuItem> <ContextMenuItem>Subscription</ContextMenuItem> </ContextMenuContent> </ContextMenu> )}Submenu
Use ContextMenuSub for secondary actions. Hover over ContextMenuSubTrigger to open the nested menu.
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem>Back</ContextMenuItem> <ContextMenuItem disabled>Forward</ContextMenuItem> <ContextMenuItem>Reload</ContextMenuItem> <ContextMenuSeparator /> <ContextMenuSub> <ContextMenuSubTrigger>More Tools</ContextMenuSubTrigger> <ContextMenuSubContent> <ContextMenuItem>Save Page As…</ContextMenuItem> <ContextMenuItem>Create Shortcut…</ContextMenuItem> <ContextMenuItem>Name Window…</ContextMenuItem> <ContextMenuSeparator /> <ContextMenuItem>Developer Tools</ContextMenuItem> </ContextMenuSubContent> </ContextMenuSub> </ContextMenuContent> </ContextMenu> )}Shortcuts
Add ContextMenuShortcut to an item to show its keyboard hint.
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuShortcut, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem> Back <ContextMenuShortcut>⌘[</ContextMenuShortcut> </ContextMenuItem> <ContextMenuItem> Forward <ContextMenuShortcut>⌘]</ContextMenuShortcut> </ContextMenuItem> <ContextMenuItem> Reload <ContextMenuShortcut>⌘R</ContextMenuShortcut> </ContextMenuItem> </ContextMenuContent> </ContextMenu> )}Groups
Organize related actions with ContextMenuGroup and ContextMenuLabel, then divide sections with ContextMenuSeparator.
import { ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuGroup> <ContextMenuLabel>Actions</ContextMenuLabel> <ContextMenuItem>Copy</ContextMenuItem> <ContextMenuItem>Cut</ContextMenuItem> <ContextMenuItem>Paste</ContextMenuItem> </ContextMenuGroup> <ContextMenuSeparator /> <ContextMenuGroup> <ContextMenuLabel>File</ContextMenuLabel> <ContextMenuItem>Save</ContextMenuItem> <ContextMenuItem>Export</ContextMenuItem> <ContextMenuItem>Print</ContextMenuItem> </ContextMenuGroup> </ContextMenuContent> </ContextMenu> )}Icons
Place an SVG icon before the label. It inherits the muted color by default.
import type { SVGProps } from "react"import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger,} from "@/components/ui/context-menu"function CopyIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <rect width="14" height="14" x="8" y="8" rx="2" ry="2" /> <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" /> </svg> )}function ClipboardIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <rect width="8" height="4" x="8" y="2" rx="1" ry="1" /> <path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2" /> </svg> )}function ScissorsIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <circle cx="6" cy="6" r="3" /> <path d="M8.12 8.12 12 12" /> <path d="M20 4 8.12 15.88" /> <circle cx="6" cy="18" r="3" /> <path d="M14.8 14.8 20 20" /> </svg> )}export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem> <CopyIcon /> Copy </ContextMenuItem> <ContextMenuItem> <ScissorsIcon /> Cut </ContextMenuItem> <ContextMenuSeparator /> <ContextMenuItem> <ClipboardIcon /> Paste </ContextMenuItem> </ContextMenuContent> </ContextMenu> )}Checkboxes
Use ContextMenuCheckboxItem for independent options. The menu stays open while you change several of them.
import * as React from "react"import { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuLabel, ContextMenuSeparator, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { const [bookmarks, setBookmarks] = React.useState(true) const [urls, setUrls] = React.useState(false) return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuLabel>Appearance</ContextMenuLabel> <ContextMenuSeparator /> <ContextMenuCheckboxItem checked={bookmarks} onCheckedChange={setBookmarks} > Show Bookmarks </ContextMenuCheckboxItem> <ContextMenuCheckboxItem checked={urls} onCheckedChange={setUrls}> Show Full URLs </ContextMenuCheckboxItem> </ContextMenuContent> </ContextMenu> )}Radio
Wrap ContextMenuRadioItem in ContextMenuRadioGroup for an exclusive choice. The menu stays open while you switch values.
import * as React from "react"import { ContextMenu, ContextMenuContent, ContextMenuLabel, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuTrigger,} from "@/components/ui/context-menu"export function Example() { const [person, setPerson] = React.useState("pedro") return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuLabel>People</ContextMenuLabel> <ContextMenuSeparator /> <ContextMenuRadioGroup value={person} onValueChange={setPerson}> <ContextMenuRadioItem value="pedro">Pedro Duarte</ContextMenuRadioItem> <ContextMenuRadioItem value="colm">Colm Tuite</ContextMenuRadioItem> <ContextMenuRadioItem value="amanda">Amanda Chen</ContextMenuRadioItem> <ContextMenuRadioItem value="jordan">Jordan Lee</ContextMenuRadioItem> <ContextMenuRadioItem value="sam">Sam Rivera</ContextMenuRadioItem> </ContextMenuRadioGroup> </ContextMenuContent> </ContextMenu> )}Destructive
Set variant="destructive" on ContextMenuItem for a dangerous action. Both the label and icon use the destructive color.
import type { SVGProps } from "react"import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger,} from "@/components/ui/context-menu"function PencilIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /> </svg> )}function ShareIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="M12 2v13" /><path d="m16 6-4-4-4 4" /><path d="M4 12v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-8" /> </svg> )}function ArchiveIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <rect width="20" height="5" x="2" y="3" rx="1" /><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8" /><path d="M10 12h4" /> </svg> )}function TrashIcon(props: SVGProps<SVGSVGElement>) { return ( <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" {...props}> <path d="M3 6h18" /><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" /><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" /> </svg> )}export function Example() { return ( <ContextMenu> <ContextMenuTrigger className="flex h-40 w-full items-center justify-center rounded-md border border-dashed border-border text-sm tracking-wide text-muted-foreground"> Right click here </ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem> <PencilIcon /> Edit </ContextMenuItem> <ContextMenuItem> <ShareIcon /> Share </ContextMenuItem> <ContextMenuItem> <ArchiveIcon /> Archive </ContextMenuItem> <ContextMenuSeparator /> <ContextMenuItem variant="destructive"> <TrashIcon /> Delete </ContextMenuItem> </ContextMenuContent> </ContextMenu> )}API Reference
ContextMenu
The root provider that wraps the trigger and content.
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | — |
<ContextMenu> <ContextMenuTrigger>Right click</ContextMenuTrigger> <ContextMenuContent> <ContextMenuItem>Open</ContextMenuItem> </ContextMenuContent></ContextMenu>ContextMenuTrigger
The surface that opens the menu on right-click or long press.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ContextMenuTrigger>Right click</ContextMenuTrigger>ContextMenuContent
The menu panel, positioned at the pointer and kept within the viewport.
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ContextMenuContent> <ContextMenuItem>Open</ContextMenuItem></ContextMenuContent>ContextMenuItem
Selecting an item closes the menu.
| Prop | Type | Default |
|---|---|---|
| variant | "default" | "destructive" | "default" |
| inset | boolean | false |
| disabled | boolean | false |
| className | string | — |
<ContextMenuItem>Open</ContextMenuItem>ContextMenuCheckboxItem
An independent option that does not close the menu when selected.
| Prop | Type | Default |
|---|---|---|
| checked | boolean | false |
| onCheckedChange | (checked: boolean) => void | — |
| disabled | boolean | false |
| className | string | — |
<ContextMenuCheckboxItem checked> Show bookmarks</ContextMenuCheckboxItem>ContextMenuRadioGroup
Provides exclusive selection for ContextMenuRadioItem.
| Prop | Type | Default |
|---|---|---|
| value | string | "" |
| onValueChange | (value: string) => void | — |
| className | string | — |
<ContextMenuRadioGroup value="mina"> <ContextMenuRadioItem value="mina">Mina</ContextMenuRadioItem></ContextMenuRadioGroup>ContextMenuRadioItem
Use inside ContextMenuRadioGroup. Selecting an item does not close the menu.
| Prop | Type | Default |
|---|---|---|
| value | string | — |
| disabled | boolean | false |
| className | string | — |
<ContextMenuRadioItem value="mina">Mina</ContextMenuRadioItem>ContextMenuLabel
| Prop | Type | Default |
|---|---|---|
| inset | boolean | false |
| className | string | — |
<ContextMenuLabel>Account</ContextMenuLabel>ContextMenuShortcut
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ContextMenuItem> Save <ContextMenuShortcut>⌘S</ContextMenuShortcut></ContextMenuItem>ContextMenuSeparator
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ContextMenuSeparator />ContextMenuGroup
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ContextMenuGroup> <ContextMenuItem>Open</ContextMenuItem></ContextMenuGroup>ContextMenuSub
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | — |
<ContextMenuSub> <ContextMenuSubTrigger>More</ContextMenuSubTrigger> <ContextMenuSubContent> <ContextMenuItem>Save</ContextMenuItem> </ContextMenuSubContent></ContextMenuSub>ContextMenuSubTrigger
| Prop | Type | Default |
|---|---|---|
| inset | boolean | false |
| className | string | — |
<ContextMenuSubTrigger>More</ContextMenuSubTrigger>ContextMenuSubContent
| Prop | Type | Default |
|---|---|---|
| className | string | — |
<ContextMenuSubContent> <ContextMenuItem>Save</ContextMenuItem></ContextMenuSubContent>