Skip to content
Drivn logoDrivn
5 min read

React Context Menu Component Examples

Drop-in React Context Menu examples: right-click menu with icons and shortcuts, nested submenus, destructive items, grouped sections, and disabled state.

A context menu is the floating panel that opens on right-click — the affordance editors, file managers, and board apps use to expose secondary actions without crowding the surface. The Context Menu component in Drivn is built from React state and CSS only. There is no Radix dependency, no portal, no floating-ui — the menu is a fixed-positioned div placed at e.clientX / e.clientY from the onContextMenu event, and it closes on any mousedown outside or on Escape.

This page collects the patterns that ship in real applications. Each snippet imports from @/components/ui/context-menu, the same path the Drivn CLI writes the file to, so you can paste them into a Next.js or Vite project after npx drivn add context-menu and they compile without edits. The component lives in your repo after install — the runtime cost is zero new dependencies, just a few hundred lines of plain TSX you own end to end.

The examples below cover the common cases: a basic right-click menu with icons, items with keyboard shortcut hints, nested submenus via dot notation, destructive and disabled item states, and groups with labels. Pick the one closest to your use case and change the items to whatever your app needs to expose. To compare the API against the shadcn/ui equivalent, see Drivn vs shadcn Context Menu.

Basic right-click menu with icons

The default Context Menu wraps any region with a Trigger and renders the menu list inside Content. Right-click anywhere inside the trigger and the menu opens at the cursor; click outside or press Escape and it closes. Pass an icon prop on Item and the source renders it via React.isValidElement(Icon) ? Icon : <Icon className="w-4 h-4" />, so you can hand it either a component reference (icon={Copy}) or a fully styled element (icon={<Copy className="w-4 h-4 text-blue-500" />}). The structure mirrors the Context Menu docs usage and matches what npx drivn add context-menu writes into your project.

1import { ContextMenu } from "@/components/ui/context-menu"
2import { Copy, Trash2 } from "lucide-react"
3
4export default function Page() {
5 return (
6 <ContextMenu>
7 <ContextMenu.Trigger>
8 <div>Right-click here</div>
9 </ContextMenu.Trigger>
10 <ContextMenu.Content>
11 <ContextMenu.Item icon={Copy}>
12 Copy
13 </ContextMenu.Item>
14 <ContextMenu.Item icon={Trash2} destructive>
15 Delete
16 </ContextMenu.Item>
17 </ContextMenu.Content>
18 </ContextMenu>
19 )
20}

Items with keyboard shortcut hints

The shortcut prop on Item renders a right-aligned muted span — the source applies ml-auto text-xs text-muted-foreground so the hint sits opposite the label inside the same row. The text is purely visual; there is no key handling attached. Pair it with the actual hotkey logic in your app — a useEffect that listens for keydown and dispatches the same handler the menu item triggers. The shortcut string supports any glyph including the macOS Command symbol ⌘, Control ⌃, Shift ⇧, and Option ⌥. The example below renders ⌘X, ⌘C, and ⌘V next to Cut, Copy, and Paste — the canonical clipboard set most editor menus expose.

1<ContextMenu>
2 <ContextMenu.Trigger>
3 <div>Right-click here</div>
4 </ContextMenu.Trigger>
5 <ContextMenu.Content>
6 <ContextMenu.Item icon={Scissors} shortcut="⌘X">
7 Cut
8 </ContextMenu.Item>
9 <ContextMenu.Item icon={Copy} shortcut="⌘C">
10 Copy
11 </ContextMenu.Item>
12 <ContextMenu.Item icon={Clipboard} shortcut="⌘V">
13 Paste
14 </ContextMenu.Item>
15 </ContextMenu.Content>
16</ContextMenu>

Nested submenu

Submenus open on hover. The Sub wrapper carries relative group/sub; the SubContent panel starts at opacity-0 invisible and flips to opacity-100 visible via the group-hover/sub: Tailwind variant when the parent receives hover. There is no JavaScript timer and no click trigger — the entire transition is two CSS classes plus a 150ms transition-[opacity,visibility]. SubTrigger automatically appends a ChevronRight icon on the right via <ChevronRight className="ml-auto w-4 h-4" />, matching how editor and refactor menus signal expandable rows. Compose any depth of menus by nesting another Sub inside the SubContent — the hover model continues to work down the tree.

1<ContextMenu>
2 <ContextMenu.Trigger>
3 <div>Right-click here</div>
4 </ContextMenu.Trigger>
5 <ContextMenu.Content>
6 <ContextMenu.Item icon={Pencil}>
7 Edit
8 </ContextMenu.Item>
9 <ContextMenu.Sub>
10 <ContextMenu.SubTrigger icon={FolderOpen}>
11 Refactor
12 </ContextMenu.SubTrigger>
13 <ContextMenu.SubContent>
14 <ContextMenu.Item>
15 Rename
16 </ContextMenu.Item>
17 <ContextMenu.Item>
18 Extract Component
19 </ContextMenu.Item>
20 <ContextMenu.Item>
21 Move to Folder
22 </ContextMenu.Item>
23 </ContextMenu.SubContent>
24 </ContextMenu.Sub>
25 </ContextMenu.Content>
26</ContextMenu>

Destructive and disabled items

Two boolean props handle the most common item states. destructive swaps in red-tinted styles via text-destructive hover:bg-destructive/10 — the right look for irreversible actions like Delete or Remove. disabled applies opacity-50 pointer-events-none and forwards the disabled attribute to the underlying <button>, so the row neither reacts to hover nor fires onClick. Both flags compose with icon and shortcut so a destructive shortcut item like ⌘⌫ Delete reads the way users expect. The styles object lives at the top of the source file — see the Context Menu docs if you want to add a third state like "warning" by extending styles and threading a new prop through Item.

1<ContextMenu>
2 <ContextMenu.Trigger>
3 <div>Right-click here</div>
4 </ContextMenu.Trigger>
5 <ContextMenu.Content>
6 <ContextMenu.Item icon={Star}>
7 Add to favorites
8 </ContextMenu.Item>
9 <ContextMenu.Item icon={Archive} disabled>
10 Archive
11 </ContextMenu.Item>
12 <ContextMenu.Separator />
13 <ContextMenu.Item icon={Trash2} destructive shortcut="⌘⌫">
14 Delete
15 </ContextMenu.Item>
16 </ContextMenu.Content>
17</ContextMenu>

Grouped sections with labels

Group and Label partition the menu into themed sections. Label renders a small uppercase-feel header via px-3 py-1.5 text-xs font-medium text-muted-foreground — visually distinct from items but inside the same flow. Group wraps related items with py-1 so adjacent groups breathe without needing a hard Separator. Use this pattern when the menu carries enough actions that scanning by category becomes faster than reading top to bottom — for a notes app you might split History (Undo, Redo) from Format (Bold, Italic) from Actions (Share, Delete). After the basic installation, the same pattern works for both inline and submenu variants.

1<ContextMenu>
2 <ContextMenu.Trigger>
3 <div>Right-click here</div>
4 </ContextMenu.Trigger>
5 <ContextMenu.Content>
6 <ContextMenu.Group>
7 <ContextMenu.Label>Actions</ContextMenu.Label>
8 <ContextMenu.Item icon={Undo2} shortcut="⌘Z">
9 Undo
10 </ContextMenu.Item>
11 <ContextMenu.Item icon={Redo2} shortcut="⌘⇧Z">
12 Redo
13 </ContextMenu.Item>
14 </ContextMenu.Group>
15 </ContextMenu.Content>
16</ContextMenu>
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

Yes. The component imports React, the ChevronRight icon from lucide-react, and the local cn utility — nothing else. State lives in a small Context provider, the menu is a fixed-positioned div placed at the cursor coordinates from the onContextMenu event, and submenus open via the group-hover/sub CSS selector. There is no portal layer and no floating-ui, so the entire feature ships in roughly two hundred lines of TSX you own after install.

The ContextMenuRoot component already handles it. The internal onContextMenu callback calls e.preventDefault() before opening the Drivn menu, so the native browser menu never appears inside the trigger region. Anywhere outside the trigger, the browser default still works — that is the right behavior, since Drivn should not hijack right-click globally. Wrap only the regions where you want the custom menu to take over.

Not in the current implementation. Drivn's submenu opens via group-hover/sub CSS, which means the panel becomes visible while the cursor is over the parent Sub. Click-to-open submenus would require additional state per submenu and a click handler on SubTrigger. If your app needs that pattern, the Context Menu file is small enough to extend — store an open state on the Sub, toggle it from SubTrigger, and replace the group-hover/sub: classes with a conditional class on SubContent.

Yes. The Item component's onClick handler calls your onClick prop and then calls setOpen(false) from the Context Menu context. Both fire in sequence, so the action runs before the menu unmounts. If you need to keep the menu open after a click — for a multi-select pattern, for example — wrap the action in a custom item that does not close the menu, or render a checkbox row inside Item and stop propagation on the inner change handler.

Drivn does not include automatic edge-collision detection. The menu renders at { left: pos.x, top: pos.y } from the click coordinates, so right-clicking near the bottom-right corner of the page can clip the menu. For most app shells this is fine because triggers are inside content regions with margin to spare. If you need clipping protection, you can override the Content className with style overrides that read window.innerWidth and clamp the position, or wrap the whole component in a useEffect that reads getBoundingClientRect after open and adjusts.