How to Add a Command Menu to a React App
Step-by-step guide to adding a copy-and-own Command menu to any React app with the Drivn CLI — cmdk-powered search, a ⌘K palette, and groups.
A command menu — the searchable list that powers Spotlight, the VS Code palette, and the ⌘K menus inside Linear and Vercel — is one of those features that looks simple and turns out to involve real keyboard, filtering, and focus work. Fuzzy matching, arrow-key navigation, selected-item state, and an accessible listbox role all have to line up. Most teams reach for a library rather than rebuild it, and the question becomes which one and how much API you inherit.
Drivn answers it by writing the component into your own repository instead of installing a runtime package. The CLI copies a command.tsx file that wraps cmdk — the battle-tested command primitive — in a thin, fully typed surface with a dot-notation API: Command.Input, Command.List, Command.Empty, Command.Group, Command.Item, Command.Separator, Command.Shortcut, and Command.Dialog. After install the file is yours to read end to end and restyle. Because cmdk manages selection state with hooks, the component is marked 'use client', but the page around it can still render on the server.
This guide adds the Command menu to an existing React app — install the CLI, render an inline searchable menu with controlled state, build a ⌘K palette with Command.Dialog, then customize groups, shortcuts, and styles. It works the same in Vite + React or a Next.js App Router app. For the Next.js notes see the Command docs; for live patterns see the Command examples.
Prerequisites
Before installing the Command 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 Command pulls in cmdk for the search and keyboard logic and lucide-react for the input search icon. It also composes the Drivn Dialog for its Command.Dialog palette variant, so the CLI installs that file alongside it.
Step 1 — Install Drivn via the CLI
Run the CLI from your project root to add the Command source file. The command prompts once for your install directory (defaulting to src/components/ui/), writes command.tsx, and adds cmdk and lucide-react to your package.json if they are not already present. Because Command.Dialog imports the Drivn Dialog, the CLI resolves that internal dependency and installs dialog.tsx too. No global config file is created — the Command is a TypeScript file in your repo that you edit like any other component. Confirm the file landed in your editor, then commit it. 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 Command menu to your existing React project 2 npx drivn add command 3 4 # verify the file was written 5 ls src/components/ui/command.tsx
Step 3 — Build a ⌘K command palette
For the floating Spotlight-style palette, swap Command for Command.Dialog, exposed through the same import via Object.assign. It takes controlled open and onOpenChange props and renders the menu inside the Drivn Dialog at max-w-lg. The sub-component also registers a keyboard listener in a useEffect that toggles the palette on ⌘K (or Ctrl+K on Windows and Linux), so the shortcut works the moment the component mounts — you do not wire it up yourself. Hold the open state in useState and bind it. Everything inside — Command.Input, Command.List, groups, and items — composes exactly as in the inline menu. The examples page shows a full palette with multiple groups.
1 'use client' 2 import { useState } from 'react' 3 import { Command } from '@/components/ui/command' 4 5 export function CommandMenu() { 6 const [open, setOpen] = useState(false) 7 return ( 8 <Command.Dialog open={open} onOpenChange={setOpen}> 9 <Command.Input placeholder="Type a command or search..." /> 10 <Command.List> 11 <Command.Empty>No results found.</Command.Empty> 12 <Command.Group heading="Suggestions"> 13 <Command.Item>Profile</Command.Item> 14 <Command.Item> 15 Settings 16 <Command.Shortcut>⌘S</Command.Shortcut> 17 </Command.Item> 18 </Command.Group> 19 </Command.List> 20 </Command.Dialog> 21 ) 22 }
Step 4 — Groups, shortcuts, and separators
Three more sub-components shape a richer menu. Command.Group takes a heading and renders a labeled section — cmdk styles the heading through the [cmdk-group-heading] selector baked into the styles.group entry. Command.Separator draws a thin divider between sections. Command.Shortcut renders a <kbd> pushed to the right of an item with ml-auto, the standard place to surface a keybinding. Because the whole file lives in your codebase, customizing it is a source edit: open src/components/ui/command.tsx, find the const styles object near the top, and adjust the item, group, or list keys. Every class reads from your Tailwind tokens, so a theme change in globals re-themes every Command instance at once.
1 <Command.List> 2 <Command.Group heading="Pages"> 3 <Command.Item>Dashboard</Command.Item> 4 </Command.Group> 5 <Command.Separator /> 6 <Command.Group heading="Settings"> 7 <Command.Item> 8 Keyboard shortcuts 9 <Command.Shortcut>⌘K</Command.Shortcut> 10 </Command.Item> 11 </Command.Group> 12 </Command.List>
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 command. The Command has no dependency on Next.js or any router — it renders anywhere React and Tailwind reach the DOM. Vite + React is the most common non-Next setup and works without extra configuration. The CLI also installs the Dialog component that the palette variant composes.
It is built into Command.Dialog. The sub-component runs a useEffect that adds a keydown listener checking for the k key with either metaKey (⌘ on macOS) or ctrlKey (Windows and Linux). When it fires, it calls preventDefault and toggles your open state. The listener is cleaned up on unmount, so you get the shortcut by rendering the component — no manual event handling required.
Yes, automatically. Drivn wraps cmdk, which scores each Command.Item against the current Command.Input value and hides non-matching rows. The highlighted item is tracked with a data-[selected=true] attribute that the styles target, and arrow keys move the selection. You provide the items as children; the filtering, keyboard navigation, and empty-state toggle are handled by the primitive underneath.
The Command is a client component — its source is marked 'use client' because cmdk manages search and selection state with hooks. Your surrounding page and layout still render on the server. The usual pattern is a small client component holding the menu (or a global ⌘K palette mounted in a client layout), rendered as an island inside an otherwise server-rendered route.

