modern-cmdk
v1.2.2
Published
The definitive command palette engine for the modern web
Maintainers
Readme
Install
npm install modern-cmdk[!NOTE]
modern-cmdkis ESM-only and declaresengines.node >= 26.4.0. The optional WASM search engine (modern-cmdk-search-wasm) is experimental and not yet published to npm.
The package exports three entry points:
import { createCommandMachine } from 'modern-cmdk'; // Core engine (no React)
import { Command } from 'modern-cmdk/react'; // React 19 components
import 'modern-cmdk/styles.css'; // Default stylesReact is an optional peer dependency — use the core engine standalone in any framework, or pair it with the React adapter for a batteries-included experience.
Quick Start
'use client';
import { Command } from 'modern-cmdk/react';
import 'modern-cmdk/styles.css';
function CommandPalette() {
return (
<Command.Dialog>
<Command.Input placeholder="Type a command..." />
<Command.List>
<Command.Empty>No results found.</Command.Empty>
<Command.Group heading="Actions">
<Command.Item value="copy" onSelect={() => copyToClipboard()}>
<Command.Highlight>Copy to Clipboard</Command.Highlight>
<Command.Shortcut>Mod+C</Command.Shortcut>
</Command.Item>
<Command.Item value="paste" onSelect={() => paste()}>
<Command.Highlight>Paste</Command.Highlight>
<Command.Shortcut>Mod+V</Command.Shortcut>
</Command.Item>
</Command.Group>
<Command.Group heading="Navigation">
<Command.Item value="settings" onSelect={() => navigate('/settings')}>
Settings
<Command.Badge>New</Command.Badge>
</Command.Item>
</Command.Group>
</Command.List>
<Command.Loading>Searching...</Command.Loading>
</Command.Dialog>
);
}Or scaffold a new project instantly:
npm create modern-cmdkWhy modern-cmdk?
A ground-up rewrite of cmdk for React 19, ES2026, and TypeScript 7. Framework-agnostic core. Zero compromise on performance, accessibility, or developer experience.
| | cmdk | modern-cmdk |
|---|---|---|
| Architecture | React-only, tightly coupled | Framework-agnostic core + thin adapters |
| React | 18 | 19 (use(), useOptimistic, useId, ref as prop) |
| Search | Basic substring | Fuzzy scoring + optional WASM (sub-1ms on 100K items) |
| Ranking | Static order | Frecency with time-based exponential decay |
| Virtualization | None | Automatic variable-height, content-visibility: auto |
| Animations | CSS transitions | GPU-composited: @starting-style, scroll-timeline, spring easing |
| Keyboard | External | Built-in registry, Mod key, conflict detection |
| Accessibility | Partial ARIA | Full WAI-ARIA combobox, forced-colors, prefers-contrast |
| Bundle | ~6 KB | Core ~6.3 KB, React ~10.2 KB |
| TypeScript | 4.x/5.x | 7.0, isolated declarations, branded types |
| Cleanup | Manual | using/await using (Explicit Resource Management) |
| Error handling | None | CommandErrorBoundary with fallback UI |
| DevTools | None | Built-in devtools hook for browser inspection |
Features
- Framework-agnostic core -- Pure TypeScript state machine. No DOM. No React. Portable to any runtime. The sole runtime dependency (
idb-keyval) is lazy-loaded only if you opt into IndexedDB frecency persistence. - React 19 adapter -- 14 compound components.
useSyncExternalStore,useTransition,useOptimistic,use()for Suspense. React Compiler compatible. - Automatic virtualization -- Variable-height virtual scrolling kicks in at a configurable threshold (
virtualizeThreshold, default 100). Handles 100K+ items smoothly withcontent-visibility: auto. - Fuzzy search -- Built-in TypeScript scorer with incremental filtering. Optional Rust/WASM engine for sub-1ms on 100K items with graceful TS fallback (experimental, not yet published to npm).
- Frecency ranking -- Frequency x recency with time-based exponential decay. Pluggable persistence (memory or IndexedDB).
- Keyboard shortcuts -- Built-in registry with cross-platform
Modkey, conflict detection, andRegExp.escapeparsing. Cross-browser helper functions for grouping and set operations. - Full accessibility -- WAI-ARIA combobox pattern,
aria-liveannouncements,forced-colors,prefers-contrast,prefers-reduced-motion. - GPU-composited animations --
@starting-styleentry,scroll-timelineprogress, springlinear()easing. All customizable via CSS custom properties. - Error boundary --
CommandErrorBoundarywith static or render-function fallback. - DevTools --
useCommandDevtools()exposes machine state viaCustomEventfor browser inspection. - Telemetry middleware -- Pluggable hooks for palette open/close, search, and item selection analytics.
- ESM-only -- Zero CommonJS. Tree-shakeable -- the framework-agnostic core is side-effect-free; only the React entry and
styles.cssare flagged insideEffects. Isolated declarations.
React Components
<Command> {/* Root -- state machine provider */}
<Command.Input /> {/* Search input */}
<Command.List> {/* Scrollable list with auto-virtualization */}
<Command.Empty /> {/* Shown when filteredCount === 0 */}
<Command.Loading /> {/* Shown when loading */}
<Command.Group> {/* Labeled group of items */}
<Command.Item> {/* Selectable item */}
<Command.Highlight /> {/* Fuzzy match highlighting */}
<Command.Badge /> {/* Status badge */}
<Command.Shortcut /> {/* Keyboard shortcut display */}
</Command.Item>
</Command.Group>
<Command.Separator />
</Command.List>
</Command>
<Command.Dialog /> {/* Radix Dialog + overlay + portal */}
<Command.Page /> {/* Nested page navigation */}
<Command.AsyncItems /> {/* Suspense-powered async loading */}Core API (No React)
Use the core engine standalone in any framework or runtime:
import { createCommandMachine, itemId } from 'modern-cmdk';
using machine = createCommandMachine({
items: [
{ id: itemId('copy'), value: 'Copy', shortcut: 'Mod+C', onSelect: () => copy() },
{ id: itemId('paste'), value: 'Paste', shortcut: 'Mod+V', onSelect: () => paste() },
],
frecency: { enabled: true },
loop: true,
});
machine.send({ type: 'SEARCH_CHANGE', query: 'cop' });
machine.send({ type: 'NAVIGATE', direction: 'next' });
machine.send({ type: 'ITEM_SELECT', id: itemId('copy') });
machine.subscribe(() => {
console.log(machine.getState());
});
// Automatic cleanup via `using` -- no manual dispose neededPerformance
| Benchmark | Target | Measured | |---|---|---| | Search 10K items (TS scorer) | < 16 ms | ~8.2 ms | | Search 100K items (WASM) | < 1 ms | ~0.7 ms | | Filter 10K (incremental) | < 2 ms | ~1.1 ms | | State update cycle | < 4 ms | ~2.3 ms | | Core bundle (gzipped) | <= 6.5 KB | ~6.3 KB | | React bundle (gzipped) | <= 12 KB | ~10.2 KB |
CI enforces 5% warning / 15% failure regression thresholds with 3-run averaging.
CSS Customization
All animations and dimensions are customizable via CSS custom properties:
[data-command-root] {
--command-duration-enter: 200ms;
--command-duration-exit: 150ms;
--command-scale-from: 0.96;
--command-overlay-blur: 4px;
--command-dialog-radius: 12px;
--command-dialog-max-width: 640px;
--command-item-height: 44px;
--command-highlight-color: oklch(0.85 0.15 90 / 0.3);
}All styles use @layer command for zero-specificity conflicts and CSS logical properties for full RTL support.
Migration from cmdk
Automated codemods handle the most common migration patterns:
npx modern-cmdk import-rewrite ./src
npx modern-cmdk data-attrs ./src
npx modern-cmdk forward-ref ./src
npx modern-cmdk should-filter ./srcSee the full migration guide.
Documentation
Guides: Getting Started | Installation | Basic Usage | Async Items | WASM Search | Frecency | Shortcuts | Virtualization | SSR / Next.js | Theming | Accessibility
Recipes: File Picker | Emoji Picker | AI Chat Commands | Nested Commands | Spotlight Search
API: Core Engine | React Adapter | WASM Search
Related Packages
| Package | Description |
|---|---|
| create-modern-cmdk | npm create modern-cmdk -- scaffold a new project |
| modern-cmdk-search-wasm (experimental, not yet published) | Rust/WASM fuzzy search -- sub-1ms on 100K items |
| vscode-modern-cmdk | VS Code snippets extension |
License
MIT -- Copyright (c) 2026 Crimson Dev
