modern-cmdk
The definitive command palette engine for the modern web.
npm version core size react size CI TypeScript React MIT License
Documentation · Getting Started · API Reference · Examples · Wiki
A ground-up rewrite of cmdk for React 19, ES2026, and TypeScript 6. 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 ~25.2 KB |
| TypeScript | 4.x/5.x | 6.0.1-rc, isolated declarations, branded types |
| Cleanup | Manual | using/await using (Explicit Resource Management) |
| Telemetry | None | Pluggable telemetry middleware |
| DevTools | None | Built-in devtools hook for browser inspection |
| Error handling | None | CommandErrorBoundary with fallback UI |
| Scaffolding | None | npm create modern-cmdk CLI |
| Editor | None | VS Code snippets extension |
- Framework-agnostic core -- Pure TypeScript state machine. Zero dependencies. No DOM. No React. Portable to any runtime.
- React 19 adapter --
useSyncExternalStore,useTransition,useOptimistic,useId,use()for Suspense. React Compiler compatible. - Automatic virtualization -- Variable-height virtual scrolling at configurable threshold. 100K+ items with
content-visibility: auto. - Fuzzy search -- Built-in TS scorer with incremental filtering. Optional WASM engine for sub-1ms on 100K items with graceful TS fallback.
- Frecency ranking -- Frequency x recency with time-based exponential decay and pluggable persistence (memory, IndexedDB).
- Keyboard shortcuts -- Built-in registry with cross-platform
Modkey,RegExp.escapeparsing, conflict detection. - Full accessibility -- WAI-ARIA combobox,
aria-live,aria-roledescription,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.
- ES2026 throughout -- Iterator Helpers,
using/await using,Promise.withResolvers,RegExp.escape. Cross-browser helpers for Set operations, grouping, and time calculations. - ESM-only -- Zero CommonJS. Tree-shakeable.
sideEffects: false. Isolated declarations.
pnpm add modern-cmdk
Or scaffold a new project:
npm create modern-cmdk
'use client'; import { Command } from 'modern-cmdk/react'; 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> ); }
| Package | Description | Size |
|---|---|---|
modern-cmdk |
Framework-agnostic core -- state machine, search, frecency, keyboard | ~6.3 KB |
modern-cmdk/react |
React 19 compound components -- Dialog, List, Item, Group, Input | ~25.2 KB |
modern-cmdk-search-wasm |
Rust/WASM fuzzy search -- trigram index, sub-1ms on 100K items | <= 50 KB |
modern-cmdk (codemods) |
Migration codemods from cmdk -- 4 transforms | CLI |
create-modern-cmdk |
Project scaffolding -- 3 templates (basic, dialog, full) | CLI |
vscode-command |
VS Code snippets -- 10 snippets for fast development | Extension |
+---------------------------------------------------------------+
| Your Application |
+---------------+---------------------------+-------------------+
| |
+---------------v--------------+ +---------v---------+
| modern-cmdk/react | | Future: Svelte |
| | | / Vue / Solid |
| Command.Dialog | | / Vanilla |
| Command.Input | +-------------------+
| Command.List |
| Command.Item |
| CommandErrorBoundary |
| useCommandDevtools() |
+---------------+--------------+
| useSyncExternalStore
| useTransition
+---------------v----------------------------------------------+
| modern-cmdk (core) |
| |
| +-------------+ +-------------+ +------------------+ |
| | State | | Search | | Frecency | |
| | Machine | | Engine | | Engine | |
| | (Pure TS) | | (Pluggable) | | (Time Decay) | |
| +------+------+ +------+------+ +---------+--------+ |
| +------v----------------v------------------v---------+ |
| | Command Registry & Event Emitter | |
| +------+---------------------------------+-----------+ |
| +------v-----------------------+ +-------v-----------+ |
| | Keyboard Shortcut Registry | | Scheduler | |
| +------------------------------+ +--------------------+ |
+-------------------+------------------------------------------+
| Optional
+-------------------v-------------------+
| modern-cmdk-search-wasm |
| Rust trigram index + scorer |
| Graceful TS fallback on failure |
+---------------------------------------+
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') }); const unsubscribe = machine.subscribe(() => { console.log(machine.getState()); }); // Automatic cleanup via `using` -- no manual dispose needed
<Command> {/* Root -- state machine */} <Command.Input /> {/* Search input */} <Command.List> {/* Scrollable list -- auto-virtualization */} <Command.Empty /> {/* filteredCount === 0 */} <Command.Loading />{/* state.loading === true */} <Command.Group> {/* Heading + items */} <Command.Item> {/* Selectable item */} <Command.Highlight /> {/* Fuzzy match highlight */} <Command.Badge /> {/* Status badge */} <Command.Shortcut /> {/* Keyboard shortcut */} </Command.Item> </Command.Group> <Command.Separator /> </Command.List> </Command> <Command.Dialog /> {/* Radix Dialog + overlay + portal */} <Command.Page /> {/* Nested page navigation */} <Command.AsyncItems /> {/* Suspense async loading */}
Raw filter throughput measured across 15 scenarios (100 / 1K / 10K items x 5 query types):
| Dataset | Query | cmdk | modern-cmdk | Result |
|---|---|---|---|---|
| 100 items | "app" |
0.091 ms | 0.058 ms | 1.6x faster |
| 100 items | "banana" |
0.055 ms | 0.035 ms | 1.6x faster |
| 100 items | "open settings" |
0.075 ms | 0.024 ms | 3.1x faster |
| 100 items | "dshbrd" |
0.048 ms | 0.075 ms | cmdk 1.6x |
| 100 items | "xyznotfound" |
0.027 ms | 0.020 ms | 1.3x faster |
| 1K items | "app" |
0.450 ms | 0.421 ms | 1.1x faster |
| 1K items | "banana" |
1.150 ms | 0.193 ms | 5.9x faster |
| 1K items | "open settings" |
0.455 ms | 0.215 ms | 2.1x faster |
| 1K items | "dshbrd" |
0.292 ms | 0.211 ms | 1.4x faster |
| 1K items | "xyznotfound" |
0.211 ms | 0.162 ms | 1.3x faster |
| 10K items | "app" |
3.198 ms | 1.938 ms | 1.7x faster |
| 10K items | "banana" |
2.853 ms | 1.770 ms | 1.6x faster |
| 10K items | "open settings" |
4.254 ms | 1.355 ms | 3.1x faster |
| 10K items | "dshbrd" |
2.342 ms | 1.986 ms | 1.2x faster |
| 10K items | "xyznotfound" |
1.743 ms | 1.672 ms | 1.0x faster |
modern-cmdk wins 14 of 15 benchmarks, up to 5.9x faster at scale. Search accuracy: precision >= 80%, specificity >= 90%, top result correctness verified.
| 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) | <= 25.5 KB | ~25.2 KB |
CI enforces 5% warning / 15% failure regression thresholds with 3-run averaging.
| Feature | cmdk | modern-cmdk |
|---|---|---|
| Basic command palette | Yes | Yes |
| Search filtering | Yes | Yes |
| Groups | Yes | Yes |
| Dialog mode | Yes | Yes |
| Loading state | Yes | Yes |
| Empty state | Yes | Yes |
| Keyboard navigation | Yes | Yes |
| Custom filtering | Yes | Yes |
| Nested pages | Yes | Yes |
| Separator | Yes | Yes |
| Match highlighting | -- | Yes |
| Framework-agnostic core | -- | Yes |
| Pluggable search engine | -- | Yes |
| Incremental search optimization | -- | Yes |
| Keyboard shortcut registry | -- | Yes |
| Frecency ranking | -- | Yes |
| Branded type safety | -- | Yes |
Disposable pattern (using) |
-- | Yes |
| ES2026 Iterator pipeline | -- | Yes |
| Page navigation stack | -- | Yes |
| Error boundary | -- | Yes |
11 exclusive features, 10 shared, 0 cmdk-only.
All animations and dimensions are customizable via CSS custom properties on [data-command-root]:
[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 library styles use @layer command and CSS logical properties for full RTL support.
npx modern-cmdk (codemods) --transform import-rewrite ./src npx modern-cmdk (codemods) --transform data-attrs ./src npx modern-cmdk (codemods) --transform forward-ref ./src npx modern-cmdk (codemods) --transform should-filter ./src
See the migration guide.
Guides: Getting Started | Installation | Basic Usage | Async Items | WASM Search | Frecency | Shortcuts | Virtualization | SSR / Next.js | TypeScript | Theming | Accessibility | Controlled Dialog
Recipes: File Picker | Emoji Picker | AI Chat Commands | Nested Commands | Spotlight Search
API: Core Engine | React Adapter | WASM Search
Benchmarks | Architecture | Migration from cmdk
git clone https://github.com/ABCrimson/modern-cmdk.git && cd modern-cmdk pnpm install && pnpm build && pnpm test
| Command | Description |
|---|---|
pnpm build |
Build all packages in parallel |
pnpm test |
Unit tests (Vitest 4.1, happy-dom) |
pnpm test:e2e |
E2E tests (Playwright 1.59, 3 browsers, 3 OS) |
pnpm bench |
Benchmarks (Vitest bench mode) |
pnpm lint |
Lint (Biome 2.4.6) |
pnpm typecheck |
Type-check (TypeScript 6.0.1-rc) |
pnpm size |
Bundle size budgets |
pnpm docs:dev |
Docs dev server (VitePress 2.0) |
See CONTRIBUTING.md for the full guide. See ARCHITECTURE.md for technical design.
| Tool | Version | Purpose |
|---|---|---|
| TypeScript | 6.0.1-rc | isolatedDeclarations, erasableSyntaxOnly, branded types |
| React | 19.3.0-canary | use(), useOptimistic, ref as prop, Activity API |
| Vite | 8.0.0-beta.16 | Playground dev server, HMR, build tooling |
| Node.js | >= 25.8.0 | ES2026: Iterator Helpers, Explicit Resource Management |
| Vitest | 4.1.0-beta.6 | Unit tests, benchmarks, V8 coverage |
| Playwright | 1.59.0-alpha | Cross-browser E2E (Chromium, Firefox, WebKit) |
| Biome | 2.4.6 | Lint + format (no ESLint, no Prettier) |
| tsdown | 0.21.0 | ESM builds, isolated declarations |
| pnpm | 11.0.0-alpha.13 | Workspace protocol, lockfile v10 |
| VitePress | 2.0.0-alpha.16 | Documentation, Shiki twoslash |
| Radix UI | 1.4.4-rc | Accessible dialog primitives |
| Rust + wasm-pack | Latest | WASM fuzzy search engine |
MIT -- Copyright (c) 2026 Crimson Dev