How to Add a Context Menu to a React App
Step-by-step guide to adding a copy-and-own right-click context menu to any React app with the Drivn CLI — cursor positioning, submenus, and shortcuts.
A context menu is the panel that appears on right-click — the place editors, file managers, and board apps put contextual actions like copy, rename, and delete, anchored to wherever the pointer was. Building one in React looks simple until you hit the real work: intercepting the browser's native contextmenu event, positioning a floating panel at the exact cursor coordinates, and dismissing it on the next click or Escape. Reach for a menu library and you inherit a portal, a positioning engine, and an API surface far larger than the feature needs.
Drivn answers it by writing the component into your own repository instead of installing a runtime package. The CLI copies a context-menu.tsx file with lucide-react as its only runtime dependency — no Radix, no floating-ui. A dot-notation API exposes every piece through one import: ContextMenu.Trigger, ContextMenu.Content, ContextMenu.Item, ContextMenu.Sub, ContextMenu.SubTrigger, ContextMenu.SubContent, ContextMenu.Group, ContextMenu.Label, and ContextMenu.Separator. The right-click handler, cursor positioning, and dismiss logic are all wired inside the component, so you compose menus rather than rebuild plumbing.
This guide adds the Context Menu to an existing React app — install the CLI, render a basic right-click menu, understand how it positions at the cursor, then add submenus, shortcut hints, and destructive items. It works the same in Vite + React or a Next.js App Router app. For Next.js notes see the Context Menu docs; for live patterns see the Context Menu examples.
Prerequisites
Before installing the Context Menu, confirm your React project has the three things Drivn assumes: Tailwind CSS v4 installed and processing your CSS, TypeScript configured (the component ships as a .tsx file), and a @/ path alias pointing at your source directory. If you scaffolded with create-next-app, npm create vite, or npx drivn@latest create, all three are already wired. For a custom setup, check the compilerOptions.paths entry in tsconfig.json; the installation page lists the minimal config. The Context Menu pulls in only lucide-react for the ChevronRight icon its submenu trigger renders — there is no Radix, no floating-ui, and no portal dependency to install.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Context Menu source file. The command prompts once for your install directory (defaulting to src/components/ui/), writes context-menu.tsx, and adds lucide-react to your package.json if it is not already present. No global config file is created — the Context Menu is a TypeScript file in your repo that you edit like any other component. Confirm the file landed in your editor, then commit it to keep a clean baseline before customizing. If your project uses a monorepo layout or a non-standard path, the CLI docs cover the flags for targeting a custom location during install.
1 # add the Context Menu to your existing React project 2 npx drivn add context-menu 3 4 # verify the file was written 5 ls src/components/ui/context-menu.tsx
Step 3 — How cursor positioning works
The part most implementations get wrong is positioning, and Drivn handles it inside the root component so you never touch it. An onContextMenu handler calls e.preventDefault() to suppress the browser's native menu, stores e.clientX and e.clientY in a pos state object, and sets open to true. The Content panel is fixed-positioned and applies that position as inline style={{ left: pos.x, top: pos.y }}, so it renders exactly under the pointer. Dismissal lives in a useEffect that, while open, listens for a mousedown anywhere to close and for the Escape key, removing both listeners on cleanup. The handler below is verbatim from the component source — you read it, but you do not write it.
1 // onContextMenu handler, verbatim from src/components/ui/context-menu.tsx 2 const onContextMenu = React.useCallback( 3 (e: React.MouseEvent) => { 4 e.preventDefault() 5 setPos({ x: e.clientX, y: e.clientY }) 6 setOpen(true) 7 }, [])
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
Frequently asked questions
Yes. Set the project up with TypeScript and Tailwind v4 first, then run npx drivn add context-menu. The Context Menu has no dependency on Next.js or any router — it reads the native contextmenu event and positions a fixed panel, which works anywhere React reaches the DOM. Vite + React is the most common non-Next setup and works without extra configuration. The only package it adds is lucide-react for the submenu chevron icon.
An onContextMenu handler on the trigger calls e.preventDefault() to block the browser menu, then stores e.clientX and e.clientY in a pos state object and opens the menu. The Content panel is fixed and applies that position as inline style={{ left: pos.x, top: pos.y }}, so it renders exactly where the pointer was when the user right-clicked. You never compute coordinates yourself — the component does it.
No. The menu is built with plain React and Tailwind — no Radix, no floating-ui, no popper, and no portal. Positioning comes from reading the native contextmenu event coordinates and applying them as inline styles on a fixed panel. Submenus open through CSS group-hover classes rather than JavaScript timers. The only runtime dependency the component adds is lucide-react for the ChevronRight icon on submenu triggers.
It closes automatically. The root registers a mousedown listener in a useEffect while open, so any click — including the click that triggers a menu item — dismisses the panel. An Escape keydown listener closes it too. Both listeners are removed on cleanup. You only write the action handler on each ContextMenu.Item; the open and close lifecycle is managed inside the component.

