Cmdk in React: Setup, Examples & Keyboard Navigation
Quick summary: This article teaches you how to install and configure cmdk as a React command menu (⌘K palette), wire keyboard navigation, handle search (including async results), and apply accessibility and performance best practices. Includes code examples and production-ready patterns.
Why use cmdk for a React command menu?
Cmdk is a focused, unopinionated toolkit for building command palettes and searchable menus in React. It gives you the primitives for a keyboard-first, accessible command experience without imposing UI styling or rendering logic. That means you can build a compact, high-performance palette that fits your design system, while keeping defaults minimal.
For product teams and devtools, a command menu reduces cognitive overhead and accelerates power users. Cmdk’s event and focus model streamlines keyboard navigation across items, groups, separators, and nested sections. Unlike large UI frameworks, cmdk is lightweight and composable, which helps keep bundle sizes down when building a global ⌘K menu.
Using cmdk also makes it easier to support progressive enhancements like voice-command triggers, remote suggestion feeds, or complex action routing. If you need an extensible command-layer for React, cmdk provides the building blocks without tying you into a specific component library.
Getting started: installation and basic setup
Install cmdk from npm or yarn. In most React apps (CRA, Vite, Next.js) you can add it like:
npm install cmdk
# or
yarn add cmdk
Then import and render a minimal command palette. The root component manages state, while child components define search input and items that respond to filtering:
import { Command } from 'cmdk';
function CommandMenu() {
return (
<Command>
<Command.Input placeholder="Search commands..." />
<Command.List>
<Command.Item value="open-settings">Open Settings</Command.Item>
<Command.Item value="create-note">Create Note</Command.Item>
</Command.List>
</Command>
);
}
This basic pattern gives you an accessible list, keyboard navigation, and selection events. To toggle the menu with a shortcut (e.g., ⌘K / Ctrl+K), wire a global key listener and control the Command root’s open state programmatically.
Core concepts and API: commands, items, search, and actions
Cmdk exposes a few composable primitives: the command root, an input that drives filtering, a list container, items, groups, and separators. The input broadcasts the user’s query; items can filter themselves or be filtered by the parent. Each item exposes selection and value props so you can map selections to commands or route changes.
Keep actions pure and idempotent. Instead of tightly coupling UI to navigation, have items emit semantic values and handle side effects in one place (a reducer or router handler). That helps with testing and makes keyboard-only flows predictable. Use stable keys for items and group items by intent to support quick scanning and feature discoverability.
Filtering strategies vary: you can use simple substring matching for small sets or integrate fuzzy-search libraries (Fuse.js, match-sorter) for better ranking. For very large catalogues, keep filtering off-main-thread (web worker) or fetch remote suggestions to avoid blocking input.
Advanced usage: keyboard navigation, async results, and customization
Keyboard navigation is the heart of a command palette. Cmdk provides built-in focus management and arrow-key handling; you only need to maintain an open boolean and ensure focus lands in the input when showing the palette. For global shortcuts, prefer a lightweight listener using keydown with a small utility to normalize modifiers across platforms:
useEffect(() => {
function onKey(e) {
const isMac = navigator.platform.toLowerCase().includes('mac');
if ((isMac && e.metaKey && e.key.toLowerCase() === 'k') ||
(!isMac && e.ctrlKey && e.key.toLowerCase() === 'k')) {
e.preventDefault();
setOpen(o => !o);
}
}
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, []);
To support async or remote suggestions, debounce the input and fetch results. Render a loading state within the command list and merge local and remote groups. Use keys and stable ids so cmdk can manage focus when the result set changes. Example pattern:
const [query, setQuery] = useState('');
const [remote, setRemote] = useState([]);
useEffect(() => {
const id = setTimeout(() => {
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(r => r.json())
.then(setRemote);
}, 250);
return () => clearTimeout(id);
}, [query]);
Styling and theming are intentionally manual: build components that match your design tokens. Wrap cmdk primitives in thin style components or use className props. For customization, expose render props for items to include icons, shortcuts, or labels—this enhances visual scanning without affecting keyboard behavior.
Performance, accessibility, and testing
Performance: keep the palette lightweight. Avoid heavy synchronous computations on every keystroke. Use debounce, memoization, and virtualization for very large lists. When using fuzzy search, compute results in a web worker or limit scope to top categories. Lazy-load large datasets only when the command palette is opened to save initial bundle cost.
Accessibility: cmdk is built to be screen-reader-friendly out of the box, but you must use semantic labelling. Ensure the input has an accessible placeholder and aria-label, groups have headings, and actions have descriptive labels. Manage focus: when opening the palette, move focus to the input; after an action, return focus to a sensible element or close the palette.
Testing: write unit tests that assert filtering behavior and keyboard interactions. Use integration tests (Playwright, Cypress) to validate end-to-end flows—opening via keyboard, selecting a result, and triggering the mapped command. Snapshot styles only when necessary; prefer behavior-driven assertions for maintainability.
Semantic core (keywords & clusters)
The semantic core below builds from your primary queries and expands into related and intent-based keywords to use across headings, meta, and body copy.
- Primary (high intent)
- cmdk
- cmdk React
- React command menu
- React command palette
- React ⌘K menu
- Secondary (medium intent)
- cmdk installation
- cmdk setup
- cmdk example
- cmdk tutorial
- React command menu component
- React keyboard navigation
- cmdk getting started
- Clarifying / Long-tail (low-to-medium intent)
- React searchable menu
- cmdk advanced usage
- cmdk command menu
- React command palette library
- cmdk fuzzy search
- cmdk accessibility
- cmdk async results
- cmdk focus management
- cmdk keyboard shortcuts ⌘K
Popular user questions (research snapshot)
Below are five common queries users have when evaluating or implementing cmdk in React. They were selected from typical “People Also Ask”, forums, and developer Q&A trends and can guide your FAQ and content headings.
- How do I install cmdk in a React project?
- How to implement keyboard navigation (⌘K / Ctrl+K) for cmdk?
- Can cmdk display remote or async search results?
- How do I style cmdk to match my design system?
- How do I test and ensure accessibility for a cmdk command palette?
FAQ
How do I install cmdk in a React project?
Install with npm or yarn (npm i cmdk). Import the core components from ‘cmdk’ (Command, Command.Input, Command.List, Command.Item) and render a Command root somewhere global (e.g., inside a Portal). The palette’s input drives filtering; register items with stable values and handle selection in a single handler. For a step-by-step starter, see the code examples above and the community guide at Building command menus with cmdk in React.
How do I add keyboard navigation and open the menu with ⌘K?
Listen for a global keydown and toggle your Command root’s open state when the user presses meta+K on Mac or ctrl+K on Windows/Linux. Focus the Command.Input when opening so keyboard users can start typing immediately. Rely on cmdk’s built-in arrow and Enter handling for moving between items and selecting actions. Debounce any heavy search logic so keystrokes remain snappy.
Can cmdk handle async search results (remote suggestions)?
Yes. Debounce input, fetch suggestions from your API, and render them as a separate group inside the Command list. Maintain stable keys for items so focus isn’t lost when results update. Show a loading state and handle empty results gracefully. For large datasets, prefer incremental loading or server-side ranking to keep client work minimal.
