Context Menu

An actions menu opened by right-click or long press.

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.

Right click here
tsx
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-menu

Usage

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
            └── ContextMenuItem

Basic

A straightforward menu with a few related actions. Choosing one closes the menu.

Right click here
tsx
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>  )}

Use ContextMenuSub for secondary actions. Hover over ContextMenuSubTrigger to open the nested menu.

Right click here
tsx
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.

Right click here
tsx
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.

Right click here
tsx
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.

Right click here
tsx
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.

Right click here
tsx
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.

Right click here
tsx
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.

Right click here
tsx
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.

PropTypeDefault
childrenReactNode
<ContextMenu>  <ContextMenuTrigger>Right click</ContextMenuTrigger>  <ContextMenuContent>    <ContextMenuItem>Open</ContextMenuItem>  </ContextMenuContent></ContextMenu>

ContextMenuTrigger

The surface that opens the menu on right-click or long press.

PropTypeDefault
classNamestring
<ContextMenuTrigger>Right click</ContextMenuTrigger>

ContextMenuContent

The menu panel, positioned at the pointer and kept within the viewport.

PropTypeDefault
classNamestring
<ContextMenuContent>  <ContextMenuItem>Open</ContextMenuItem></ContextMenuContent>

ContextMenuItem

Selecting an item closes the menu.

PropTypeDefault
variant"default" | "destructive""default"
insetbooleanfalse
disabledbooleanfalse
classNamestring
<ContextMenuItem>Open</ContextMenuItem>

ContextMenuCheckboxItem

An independent option that does not close the menu when selected.

PropTypeDefault
checkedbooleanfalse
onCheckedChange(checked: boolean) => void
disabledbooleanfalse
classNamestring
<ContextMenuCheckboxItem checked>  Show bookmarks</ContextMenuCheckboxItem>

ContextMenuRadioGroup

Provides exclusive selection for ContextMenuRadioItem.

PropTypeDefault
valuestring""
onValueChange(value: string) => void
classNamestring
<ContextMenuRadioGroup value="mina">  <ContextMenuRadioItem value="mina">Mina</ContextMenuRadioItem></ContextMenuRadioGroup>

ContextMenuRadioItem

Use inside ContextMenuRadioGroup. Selecting an item does not close the menu.

PropTypeDefault
valuestring
disabledbooleanfalse
classNamestring
<ContextMenuRadioItem value="mina">Mina</ContextMenuRadioItem>

ContextMenuLabel

PropTypeDefault
insetbooleanfalse
classNamestring
<ContextMenuLabel>Account</ContextMenuLabel>

ContextMenuShortcut

PropTypeDefault
classNamestring
<ContextMenuItem>  Save  <ContextMenuShortcut>⌘S</ContextMenuShortcut></ContextMenuItem>

ContextMenuSeparator

PropTypeDefault
classNamestring
<ContextMenuSeparator />

ContextMenuGroup

PropTypeDefault
classNamestring
<ContextMenuGroup>  <ContextMenuItem>Open</ContextMenuItem></ContextMenuGroup>

ContextMenuSub

PropTypeDefault
childrenReactNode
<ContextMenuSub>  <ContextMenuSubTrigger>More</ContextMenuSubTrigger>  <ContextMenuSubContent>    <ContextMenuItem>Save</ContextMenuItem>  </ContextMenuSubContent></ContextMenuSub>

ContextMenuSubTrigger

PropTypeDefault
insetbooleanfalse
classNamestring
<ContextMenuSubTrigger>More</ContextMenuSubTrigger>

ContextMenuSubContent

PropTypeDefault
classNamestring
<ContextMenuSubContent>  <ContextMenuItem>Save</ContextMenuItem></ContextMenuSubContent>