Next.js Context Menu for the App Router
Add a right-click context menu to a Next.js App Router app. Drivn ships a single-file Context Menu that positions itself at the cursor with zero runtime deps.
A context menu is the panel that appears when a user right-clicks — the place for contextual actions like copy, rename, or delete, anchored to wherever the pointer was. Building one in Next.js means 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, all without shipping a positioning library into every route.
Drivn's Context Menu does this in one file you own. After npx drivn add context-menu the source lives in src/components/ui/context-menu.tsx, marked 'use client', with lucide-react as its only runtime dependency — no Radix, no floating-ui. Dot notation exposes every piece through a single 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.
This guide covers installing Drivn in a Next.js 16 project, rendering the menu as a client island, positioning it at the cursor with the contextmenu event, and composing submenus, shortcut hints, and destructive items. Every snippet is drawn from the component's actual source. For the full reference see the Context Menu docs; for the shadcn/ui comparison see Drivn vs shadcn/ui Context Menu.
Install in a Next.js 16 project
Drivn installs through a small CLI that writes the component source straight into your repository — there is no runtime npm package to version-lock. Open a terminal at the root of your Next.js 16 project and run npx drivn add context-menu. The CLI prompts once for your install directory (defaulting to src/components/ui/), copies context-menu.tsx, and adds lucide-react to your package.json for the ChevronRight icon the submenu trigger renders. The CLI reference documents every flag, including targeting a custom path or installing several components at once. After install you own the file outright — future Drivn releases never overwrite it, and you can edit the internals without forking a dependency. Commit it first to keep a clean baseline before customizing.
1 # from the root of your Next.js 16 project 2 npx drivn add context-menu
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
The Context Menu itself is a client component — it is marked 'use client' because the root tracks the open state and cursor position with useState and registers mousedown and keydown listeners in a useEffect. Your surrounding page and layout still render on the server. The usual pattern is a small client component holding the menu and its trigger, rendered as an island inside an otherwise server-rendered route.
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.
No. The menu is built with plain React and Tailwind — no Radix, no floating-ui, no popper. 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. The only runtime dependency the component adds is lucide-react for the ChevronRight icon on submenu triggers.
The submenu is driven entirely by CSS. The Sub wrapper carries a group/sub class, and the SubContent styles use group-hover/sub:opacity-100 and group-hover/sub:visible to reveal a panel positioned left-full top-0 beside its trigger. Because hover state is handled by Tailwind variants, no hover handlers or timers run in JavaScript — the flyout appears and hides purely through the cascade.

