# Context Menu

Show actions for an element on right-click or long press.

Context menu with icons and shortcuts

[Complete TSX file](/examples/context-menu/default.tsx)

```tsx
"use client";

import { CopyIcon, PencilIcon, ShareIcon, Trash2Icon } from "lucide-react";
import {
  ContextMenu,
  ContextMenuItem,
  ContextMenuPopup,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuTrigger,
} from "@/siteplane/components/context-menu";

export default function Example() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        className="flex h-28 w-full max-w-sm items-center justify-center rounded-lg border border-dashed text-muted-foreground text-sm"
        tabIndex={0}
      >
        Right-click or long-press here
      </ContextMenuTrigger>
      <ContextMenuPopup>
        <ContextMenuItem>
          <PencilIcon aria-hidden="true" />
          Edit
          <ContextMenuShortcut>⌘E</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuItem>
          <CopyIcon aria-hidden="true" />
          Copy
          <ContextMenuShortcut>⌘C</ContextMenuShortcut>
        </ContextMenuItem>
        <ContextMenuItem>
          <ShareIcon aria-hidden="true" />
          Share
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="destructive">
          <Trash2Icon aria-hidden="true" />
          Delete
          <ContextMenuShortcut>⌘⌫</ContextMenuShortcut>
        </ContextMenuItem>
      </ContextMenuPopup>
    </ContextMenu>
  );
}
```

## Installation

Complete [one-time setup](/docs/installation/) first.

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/context-menu
```

With npm: `npx shadcn@4.18.0 add @siteplane/context-menu`. [Registry JSON](/registry/context-menu.json) lists source files and dependencies.

For this page’s examples, also install:

```bash
pnpm dlx shadcn@4.18.0 add @siteplane/card
```

## Usage

```tsx
import { PencilIcon } from "lucide-react";
import {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuPopup,
  ContextMenuItem,
} from "@/siteplane/components/context-menu";

<ContextMenu>
  <ContextMenuTrigger
    className="flex h-28 w-full items-center justify-center rounded-lg border border-dashed"
    tabIndex={0}
  >
    Right-click this area
  </ContextMenuTrigger>
  <ContextMenuPopup>
    <ContextMenuItem>
      <PencilIcon />
      Edit
    </ContextMenuItem>
  </ContextMenuPopup>
</ContextMenu>;
```

## Composition

```text
ContextMenu
├── ContextMenuTrigger
└── ContextMenuPopup
    ├── ContextMenuGroup → ContextMenuGroupLabel + items
    ├── ContextMenuItem / CheckboxItem / RadioGroup
    ├── ContextMenuSeparator
    └── ContextMenuSub → SubTrigger + ContextMenuSubPopup
```

## Groups and submenus

Label related actions with `ContextMenuGroup` and `ContextMenuGroupLabel`, and move secondary actions into a submenu.

Grouped context menu with a submenu

[Complete TSX file](/examples/context-menu/submenu.tsx)

```tsx
"use client";

import {
  CopyIcon,
  MailIcon,
  PencilIcon,
  ShareIcon,
  Trash2Icon,
} from "lucide-react";
import {
  ContextMenu,
  ContextMenuGroup,
  ContextMenuGroupLabel,
  ContextMenuItem,
  ContextMenuPopup,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuSub,
  ContextMenuSubPopup,
  ContextMenuSubTrigger,
  ContextMenuTrigger,
} from "@/siteplane/components/context-menu";

export default function Example() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        className="flex h-28 w-full max-w-sm items-center justify-center rounded-lg border border-dashed text-muted-foreground text-sm"
        tabIndex={0}
      >
        Right-click or long-press here
      </ContextMenuTrigger>
      <ContextMenuPopup>
        <ContextMenuGroup>
          <ContextMenuGroupLabel>File</ContextMenuGroupLabel>
          <ContextMenuItem>
            <PencilIcon />
            Rename
          </ContextMenuItem>
          <ContextMenuItem>
            <CopyIcon />
            Duplicate
            <ContextMenuShortcut>⌘D</ContextMenuShortcut>
          </ContextMenuItem>
          <ContextMenuSub>
            <ContextMenuSubTrigger>
              <ShareIcon />
              Share
            </ContextMenuSubTrigger>
            <ContextMenuSubPopup>
              <ContextMenuItem>
                <MailIcon />
                Email link
              </ContextMenuItem>
              <ContextMenuItem>
                <CopyIcon />
                Copy link
              </ContextMenuItem>
            </ContextMenuSubPopup>
          </ContextMenuSub>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuItem variant="destructive">
          <Trash2Icon />
          Move to trash
        </ContextMenuItem>
      </ContextMenuPopup>
    </ContextMenu>
  );
}
```

## Checkbox and radio items

Use checkbox items for view toggles and a radio group for a single choice, each under a group label. `variant="switch"` only changes the indicator. The check draws in and out like the [Checkbox](/docs/components/checkbox/) check and appears instantly under reduced motion. A radio item shows its check in the leading slot; for a row that leads with a logo or an avatar, `indicatorPosition="end"` moves the check to the end of the row, as on [Menu](/docs/components/menu/#checkbox-and-radio-items).

View options context menu

[Complete TSX file](/examples/context-menu/choices.tsx)

```tsx
"use client";

import { useState } from "react";
import {
  ContextMenu,
  ContextMenuCheckboxItem,
  ContextMenuGroup,
  ContextMenuGroupLabel,
  ContextMenuPopup,
  ContextMenuRadioGroup,
  ContextMenuRadioItem,
  ContextMenuSeparator,
  ContextMenuTrigger,
} from "@/siteplane/components/context-menu";

export default function Example() {
  const [showHidden, setShowHidden] = useState(false);
  const [sortBy, setSortBy] = useState("name");
  return (
    <ContextMenu>
      <ContextMenuTrigger
        className="flex h-28 w-full max-w-sm items-center justify-center rounded-lg border border-dashed text-muted-foreground text-sm"
        tabIndex={0}
      >
        Right-click or long-press here
      </ContextMenuTrigger>
      <ContextMenuPopup>
        <ContextMenuGroup>
          <ContextMenuGroupLabel>View</ContextMenuGroupLabel>
          <ContextMenuCheckboxItem
            checked={showHidden}
            onCheckedChange={setShowHidden}
          >
            Show hidden files
          </ContextMenuCheckboxItem>
          <ContextMenuCheckboxItem defaultChecked>
            Show file extensions
          </ContextMenuCheckboxItem>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuGroup>
          <ContextMenuGroupLabel>Sort by</ContextMenuGroupLabel>
          <ContextMenuRadioGroup value={sortBy} onValueChange={setSortBy}>
            <ContextMenuRadioItem value="name">Name</ContextMenuRadioItem>
            <ContextMenuRadioItem value="modified">
              Date modified
            </ContextMenuRadioItem>
            <ContextMenuRadioItem value="size">Size</ContextMenuRadioItem>
          </ContextMenuRadioGroup>
        </ContextMenuGroup>
        <ContextMenuSeparator />
        <ContextMenuCheckboxItem variant="switch" defaultChecked>
          Compact view
        </ContextMenuCheckboxItem>
      </ContextMenuPopup>
    </ContextMenu>
  );
}
```

## Link items

Use `ContextMenuLinkItem` for navigation; it renders a real link and closes the menu after activation.

Context menu with link items

[Complete TSX file](/examples/context-menu/with-link.tsx)

```tsx
"use client";

import { ExternalLinkIcon, SettingsIcon } from "lucide-react";
import {
  ContextMenu,
  ContextMenuLinkItem,
  ContextMenuPopup,
  ContextMenuTrigger,
} from "@/siteplane/components/context-menu";

export default function Example() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        className="flex h-28 w-full max-w-sm items-center justify-center rounded-lg border border-dashed text-muted-foreground text-sm"
        tabIndex={0}
      >
        Right-click or long-press here
      </ContextMenuTrigger>
      <ContextMenuPopup>
        <ContextMenuLinkItem href="#project">
          <ExternalLinkIcon />
          Open project
        </ContextMenuLinkItem>
        <ContextMenuLinkItem href="#project-settings">
          <SettingsIcon />
          Project settings
        </ContextMenuLinkItem>
      </ContextMenuPopup>
    </ContextMenu>
  );
}
```

## Glass

Set `variant="glass"` on `ContextMenuPopup` when the menu opens over a photo, a video or a color surface, and on a `ContextMenuSubPopup` so its submenu matches. Rows, icons and their states stay the same. [Glass](/docs/components/glass/) shows every component with a glass variant.

Glass context menu

[Complete TSX file](/examples/context-menu/glass.tsx)

```tsx
"use client";

import { CopyIcon, PencilIcon, ShareIcon, Trash2Icon } from "lucide-react";
import { Card } from "@/siteplane/components/card";
import {
  ContextMenu,
  ContextMenuItem,
  ContextMenuPopup,
  ContextMenuSeparator,
  ContextMenuTrigger,
} from "@/siteplane/components/context-menu";

export default function Example() {
  return (
    <ContextMenu>
      <ContextMenuTrigger
        className="h-28 w-full max-w-sm items-center justify-center text-sm"
        render={<Card variant="glass" />}
        tabIndex={0}
      >
        Right-click or long-press here
      </ContextMenuTrigger>
      <ContextMenuPopup variant="glass">
        <ContextMenuItem>
          <PencilIcon aria-hidden="true" />
          Edit
        </ContextMenuItem>
        <ContextMenuItem>
          <CopyIcon aria-hidden="true" />
          Copy
        </ContextMenuItem>
        <ContextMenuItem>
          <ShareIcon aria-hidden="true" />
          Share
        </ContextMenuItem>
        <ContextMenuSeparator />
        <ContextMenuItem variant="destructive">
          <Trash2Icon aria-hidden="true" />
          Delete
        </ContextMenuItem>
      </ContextMenuPopup>
    </ContextMenu>
  );
}
```

## API reference

ContextMenu, ContextMenuPortal and the group, radio and submenu parts follow [Base UI Context Menu](https://base-ui.com/react/components/context-menu). Popup includes its Portal, Positioner and a scrolling viewport; do not add another Portal around it. LinkItem renders a native link. Shortcut renders `kbd` and does not register a shortcut. SubPopup opens on the inline-end side. ContextMenuPrimitive exports the Base UI namespace. Styled className props are strings.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| ContextMenuPopup.side | Positioner side | bottom | Preferred edge. |
| ContextMenuPopup.align | start \| center \| end | start | Alignment. |
| ContextMenuPopup.sideOffset | Positioner offset | 0 | Gap. |
| ContextMenuPopup.alignOffset | Positioner offset | unset | Alignment offset. |
| ContextMenuPopup.width | auto \| trigger \| content \| at-least-trigger | auto | trigger matches the width of the ContextMenuTrigger area; at-least-trigger is at least that wide. With a custom anchor, both use the anchor's width. Every width but trigger is at most 20rem, or the trigger area's width for at-least-trigger when it is wider, and stays within the available space; a longer row label ends in an ellipsis and a group label wraps. |
| ContextMenuPopup.anchor | Positioner anchor | pointer position | Custom anchor. |
| ContextMenuPopup.portalProps | Portal.Props | unset | Portal options. |
| ContextMenuPopup.variant | default \| glass | default | glass shows a tinted, blurred view of what lies behind the popup. |
| ContextMenuItem.variant | default \| destructive | default | Action tone. A destructive item colors its label and its icon red. |
| Item / GroupLabel / SubTrigger.inset | boolean | unset (off) | Reserve the icon slot so the label lines up with icon rows. |
| ContextMenuCheckboxItem.variant | default \| switch | default | Check or switch indicator; retains menuitemcheckbox semantics. |
| ContextMenuRadioItem.indicatorPosition | start \| end | start | Where the check shows: in the leading slot, or at the end of an identity row that leads with a logo or an avatar. |
| ContextMenuCheckboxItem.size | sm \| md \| lg \| xl | lg | Switch size. Menu rows stay compact at every control size, so the switch does not follow the provider. |
| ContextMenuSubPopup.align / sideOffset | alignment / offset | start / 0 | Submenu positioning. |
| ContextMenuSubPopup.alignOffset | offset | -5 unless align=center | Submenu alignment correction. |
| ContextMenuSubPopup.variant | default \| glass | default | Set glass to match a glass parent menu. |
| open / defaultOpen | boolean | false | Visibility. |
| onOpenChange | (open, eventDetails) => void | unset | Visibility callback. |
| Item.onClick | MouseEventHandler | unset | Application action. |
| CheckboxItem.checked / onCheckedChange | boolean / callback | uncontrolled / unset | Controlled checked state. |
| RadioGroup.value / onValueChange | string / callback | uncontrolled / unset | Controlled choice. |
| ContextMenuLinkItem.closeOnClick | boolean | true | Closes the menu after activating a link. |
| ContextMenuLinkItem.variant / inset | tone / boolean | default / unset | Link-row presentation. |

Modal, disabled, focus, dismissal and close-on-click behavior follow Base UI. A disabled item cannot be activated; the submenu trigger supplies the chevron. SubPopup accepts width plus the listed offsets, not arbitrary portal or anchor options.

[Read the installed source](/source/context-menu).

## Usage notes

Start every action item with a fitting lucide icon, such as `PencilIcon` for Edit or `Trash2Icon` for Delete; checkbox and radio items show their own indicator instead. Provide the same actions through a visible button menu for users who do not discover right-click. Context Menu is not a replacement for all primary actions. Arrow keys move through the items, including disabled ones, which cannot be activated. Enter activates an item, and Escape closes the menu with focus restoration. Use real links for navigation. Selection here is menu state, not native form submission.
