cmdk in React: Build a Fast Command Palette (Setup & Examples)
Introduction to cmdk and command menus
cmdk is a lightweight command menu toolkit that makes building searchable command palettes (the kind toggled by ⌘K or Ctrl+K) straightforward in React. The pattern is simple: an input for queries, a list of actionable items that filter live, and keyboard-first navigation. For many apps, a well-built command palette becomes the fastest navigation and command surface.
Why use cmdk instead of building from scratch? cmdk provides focused primitives (input, list, items) and predictable keyboard behavior so you don’t have to reimplement roving focus, basic keyboard handling, or selection mechanics. That speeds development while keeping your UI accessible and responsive.
This guide assumes basic React experience. You’ll get installation steps, a minimal example (React command menu component), keyboard handling for React ⌘K menu behavior, and practical advanced scenarios like async search, nested commands, and virtualization. There are links to a hands-on tutorial for a full walkthrough and the React docs when relevant.
Installing and setting up cmdk in React
Start by installing the package into your project. In most cases:
npm install cmdk
# or
yarn add cmdk
Then import the primitives you’ll use. A typical import looks like:
import {
Command,
CommandInput,
CommandList,
CommandItem
} from 'cmdk';
Manage the open state with React hooks and add a global key listener for ⌘/Ctrl+K. Keep the input focused on open so keyboard users can start typing immediately. For a step-by-step walkthrough and implementation tips, see this cmdk tutorial. (link: cmdk tutorial)
Building a basic command palette (example)
Here’s a minimal React command menu component using cmdk. It demonstrates essential wiring and keyboard opening logic for React ⌘K menu behavior.
function CommandPalette() {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
function onKey(e) {
const mod = e.metaKey || e.ctrlKey;
if (mod && e.key.toLowerCase() === 'k') {
e.preventDefault();
setOpen(o => !o);
}
if (e.key === 'Escape') setOpen(false);
}
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, []);
return (
<Command open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandItem onSelect={() => console.log('Go to Dashboard')}>Go to Dashboard</CommandItem>
<CommandItem onSelect={() => console.log('Open Settings')}>Open Settings</CommandItem>
</CommandList>
</Command>
);
}
This example is intentionally small: cmdk handles basic keyboard navigation and selection inside CommandList. Your real app will supply dynamic items, groupings, icons, and custom renderers, but the core pattern stays the same: controlled open state, an input, and items.
When adding this to a production app, consider placing the Command UI in a portal to avoid stacking context issues and to ensure it overlays properly. Ensure you style the input and list to match your design system while keeping focus outlines visible for accessibility.
Advanced usage: nested commands, custom rendering, async data
cmdk is flexible: you can show grouped items, nested submenus, or custom item renderers with icons and shortcuts. For nested flows, either change the data source when an item opens a subview, or maintain a breadcrumb-like stack of contexts. Make the transition clear visually and preserve keyboard navigation semantics.
For async data (searching remote APIs), debounce the input and show a loading state. Use stable IDs for list items so virtualized list libraries can manage DOM recycling. A common pattern: maintain a “results” state, set a loading flag while fetching, then update results and let cmdk render them.
Virtualization becomes necessary for thousands of items. Use a virtual list (react-window, react-virtual) and map the visible items to CommandItem. Remember: virtualized lists may change how focus and keyboard movement is handled, so test thoroughly for arrow key navigation and screen reader behavior.
Keyboard navigation, accessibility, and performance
Keyboard-first design is essential. The command palette must open via keyboard (⌘/Ctrl+K), allow typing immediately, support arrow navigation, Enter to select, and Escape to close. Add an explicit close button for mouse users. Ensure focus returns to a sensible place after closing—for example, the element that opened the palette.
For ARIA, ensure the input has accessible labeling and that your list communicates selection. If cmdk exposes built-in ARIA attributes, prefer the library defaults. If you implement custom behaviors, follow WAI-ARIA practices for listbox or menu roles and manage aria-activedescendant when necessary.
Performance tips: keep the command list render cheap (memoize renderers), debounce search inputs, lazy-load heavy icon sets, and virtualize large collections. Avoid expensive synchronous work on keydown handlers to keep the UI snappy for keyboard users.
- ⌘/Ctrl + K — toggle command palette
- ↑ / ↓ — navigate items
- Enter — activate item
- Esc — close palette
Troubleshooting, tips, and integrations
Common issues include focus not moving to the input, palette appearing under other elements, and SSR mismatches. Fix focus by calling inputRef.current?.focus() on open. Use portals (ReactDOM.createPortal) if z-index/overflow hide the palette. For SSR, guard document/window access and initialize listeners in useEffect.
Styling integration: cmdk renders primitives, so you can style via CSS modules, Tailwind, Emotion/styled, or plain CSS. When integrating with a design system, create a wrapper that maps your theme tokens to cmdk primitives for consistent visuals across the app.
Integrations: connect the palette to your router (push history on select), to state managers for actions (dispatch), or to analytics to track which commands are frequent. Test command flows with keyboard-only and screen reader users to ensure the UX is genuinely accessible.
For additional reading, consult the official React docs on accessibility and keyboard events, and follow a practical step-by-step cmdk tutorial that walks through build details.
Practical patterns and sample code snippets
Use these snippets as templates to extend functionality: debounced async search, grouped results, and item shortcuts. The patterns below assume the earlier import of cmdk primitives.
// Debounced async search (concept)
const [query, setQuery] = React.useState('');
const [results, setResults] = React.useState([]);
React.useEffect(() => {
const t = setTimeout(() => {
if (!query) return setResults([]);
fetch('/api/search?q=' + encodeURIComponent(query))
.then(r => r.json())
.then(setResults);
}, 250);
return () => clearTimeout(t);
}, [query]);
Replace fetch with your data layer and add a loading indicator while waiting. Always handle errors and empty states gracefully to avoid a jarring UX.
If you need grouped sections:
<CommandList>
<CommandGroup heading="Pages">
<CommandItem>Dashboard</CommandItem>
</CommandGroup>
<CommandGroup heading="Actions">
<CommandItem>Log out</CommandItem>
</CommandGroup>
</CommandList>
FAQ
How do I install and set up cmdk in a React project?
Install with npm or yarn (npm install cmdk), import the core components (Command, CommandInput, CommandList, CommandItem), and manage the open state with React useState. Add a global keydown listener to toggle the menu with ⌘/Ctrl+K and focus the input on open. For a guided example, check the tutorial linked above.
How can I make cmdk keyboard-accessible and support ⌘K?
Add a global keydown handler (useEffect) that listens for metaKey/ctrlKey + ‘k’ and toggles the open state. On open, focus the CommandInput. Ensure the list supports arrow navigation and Enter to activate items; cmdk handles much of this, but validate with keyboard-only testing. Return focus to the triggering element when the palette closes.
Can cmdk handle async search and large result sets?
Yes. Debounce input updates, fetch suggestions asynchronously, display a loading state, and virtualize very large lists with libraries like react-window. Keep item renderers lightweight and provide a clear “no results” message when queries return nothing.
Semantic Core (keyword clusters)
Primary keywords
- cmdk
- cmdk React
- React command menu
- React command palette
Secondary / intent-based queries
- cmdk installation
- cmdk setup
- cmdk getting started
- React ⌘K menu
- React command menu component
- cmdk tutorial
Clarifying / long-tail / LSI phrases
- cmdk command menu
- React searchable menu
- React keyboard navigation
- cmdk example
- cmdk advanced usage
- React command palette library


Recent Comments