Skip to content

Command palette — comparison

The "⌘K" command palette — a centered modal overlay with a search box over a filtered, keyboard-navigable list of commands — is a staple of modern apps (VS Code, Linear, Raycast, GitHub, Vercel). The pattern has one dominant library, cmdk, and a scattering of single-framework alternatives and community ports around it. What no one ships is one accessible command palette that spans React and Vue and Svelte and Angular (and Solid and Lit) with a single API. That gap is what @rozie-ui/command-palette fills: one CommandPalette.rozie source compiled to six idiomatic, accessible packages.

Research snapshot: 2026-06-25. Library names, framework coverage, versions, and maintenance notes are as of that date and move fast — verify before depending on a specific claim.

The landscape at a glance

LibraryFramework(s)Headless?AccessibilityMaintained?Filtering / feature depthNotes
cmdkReact only✅ unstyledStrong — combobox/listbox roles, used by shadcn/ui CommandYes — v1.1.1 (Mar 2025)Deep: composable groups, built-in fuzzy scoring, async loading statesThe de-facto ⌘K. Powers shadcn/ui Command and Vercel. React-only.
kbarReact only⚠️ partly styledKeyboard-first; less ARIA-complete than cmdkSlowing — 0.1.0-beta.x for yearsAction registry, nested actions, built-in shortcut bindingDifferent model: a global action registry, not a controlled list. Still beta.
react-command-paletteReact only⚠️ themedWAI-ARIA compliant (its headline feature)Stale — v0.22.1, last publish ~3 yr agoFuzzy search via options; theming presetsOlder, theme-driven; largely superseded by cmdk in practice.
vue-command-paletteVue 3 only✅ unstyledCombobox-style; less documented than cmdkStale — v0.2.3, last publish ~3 yr agoComposable, cmdk-inspired; BYO filtering/scoringA community port of cmdk's ideas to Vue. Vue-only.
cmdk-svSvelte only✅ unstyledCombobox/listbox rolesDeprecated — folded into Bits UI Commandcmdk-parity scoring + groupsOfficially deprecated in favour of Bits UI Command.
svelte-command-paletteSvelte only⚠️ themedKeyboard-driven; modest ARIAOlder (v2.x)Action-list model + shortcut bindingSvelte-only; closer to kbar's action model than cmdk's.
Angular CDK Dialog + ListboxAngular only✅ primitivesStrong if assembled correctlyYes (Angular CDK)You assemble it — filtering/scoring is yours to writeNo dominant Angular command palette; usually hand-assembled from CDK primitives.
ninja-keysWeb component (HTML/Vue/React/Svelte)❌ styled elementKeyboard nav; not a documented APG-grade a11y storyLow activity — no GitHub releases, ~2023-eraNested menus, hotkeys, theming, root searchCross-framework, but a styled <ninja-keys> element you configure via a data array — a different authoring model.
Algolia DocSearchAny (drop-in widget)❌ hosted widgetGood, but a search box, not a command runnerYes (v4)Crawls + indexes your docs; instant searchAdjacent category: ⌘K site search, not an app command menu. Hosted Algolia index.
@rozie-ui/command-paletteReact, Vue, Svelte, Angular, Solid, Lit✅ headlessWAI-ARIA dialog + combobox + listbox/option, aria-activedescendantNew — 0.1.0Substring match over label + keywords, source-order; flat listOne .rozie source → six idiomatic packages, same API.

The honest core point

The dominant command palette, cmdk, is React-only. Everything else in the table is either single-framework (kbar, vue-command-palette, svelte-command-palette, the Angular CDK assembly), a community port that diverges in API and accessibility (vue-command-palette reinterprets cmdk for Vue; cmdk-sv ported it to Svelte and is now deprecated in favour of Bits UI's Command), or a cross-framework option with a fundamentally different authoring model (ninja-keys is a styled web component configured through a JS data array, not a headless primitive you compose).

So a design system that ships React and Vue and Svelte and Angular wrappers today maintains three or four different command-menu integrations — different props, different filtering models, different accessibility quality — that must somehow stay behaviorally in lockstep. That per-framework duplication is exactly what Rozie exists to eliminate: @rozie-ui/command-palette authors the WAI-ARIA command-menu behavior once and compiles it to all six targets as the same idiomatic <CommandPalette>.

What @rozie-ui/command-palette gives you

  • One source, six idiomatic packages. CommandPalette.rozie compiles to @rozie-ui/command-palette-{react,vue,svelte,angular,solid,lit}. Install only your framework's; there is no Rozie toolchain or build step for the consumer.
  • The same accessibility everywhere. A role="dialog" aria-modal overlay, an <input role="combobox" aria-autocomplete="list" aria-expanded aria-controls aria-activedescendant> search field, a role="listbox" of role="option" results with aria-selected / aria-disabled, a roving highlight tracked via aria-activedescendant while DOM focus stays on the input, Enter-to-select, Escape / backdrop to dismiss — the WAI-ARIA APG combobox-with-listbox pattern, authored once and identical across targets.
  • Idiomatic two-way state. Two model: true slices — open (visibility) and query (search text) — bound with r-model:open / v-model:open / bind:open / [(open)] and likewise for query. No per-framework controlled/uncontrolled glue. (Because there are two models, the Angular output deliberately ships no ControlValueAccessor — a palette is not a single form value.)
  • Scoped slots. option (custom row render, scoped with { option, index, active, selected, disabled } — the listbox vocabulary shared with @rozie-ui/listbox, which the palette composes internally), empty (the no-results state, scoped with { query }), and footer (a persistent footer bar) — ordinary scoped slots on every target.
  • An imperative handle. show() / close() / toggle() / focus(), obtained through each framework's native ref mechanism. (The open verb is show, not open, because an open() verb would collide with the open model.)
  • A web-component build. The Lit leaf gives plain-HTML / web-component consumers a real <rozie-command-palette> element with the same headless ARIA behavior — the one tier where the only real off-the-shelf option, ninja-keys, is a pre-styled element rather than a headless primitive.
  • Token-themed. Every visual value is a --rozie-command-palette-* CSS custom property with a fallback; shadcn/ui, Material 3, and Bootstrap 5 theme bridges ship in the box.

When a competitor is the better pick

This comparison stays credible by saying so plainly:

  • A React-only app that wants depth → use cmdk. cmdk is mature, battle-tested, powers shadcn/ui's Command, and is genuinely deeper than Rozie's v1: a fully composable group/item API, sophisticated built-in fuzzy scoring (not just substring matching), and built-in async loading states. If you will only ever ship React, cmdk is the stronger, more proven choice — Rozie's pitch is cross-framework parity, not out-featuring cmdk on its home turf.
  • A Svelte-only app → use Bits UI Command. With cmdk-sv deprecated, Bits UI's Command is the maintained Svelte option, with a built-in scoring algorithm, Command.Group headings, and loading/empty states.
  • A Vue-only app → Headless UI's combobox or vue-command-palette are reasonable, framework-native picks.
  • You only need site/docs search → use Algolia DocSearch. DocSearch is a different category — a hosted, crawled ⌘K search widget — but if "⌘K" for you means "search my documentation," it is the purpose-built tool, not an app command runner.

Rozie's wedge is consistency and coverage, not feature maximalism: there is no other accessible command palette that spans all six frameworks with the same API, and Lit / web components and Solid in particular have essentially no headless incumbent at all.

What it deliberately does not do (v1)

Stated plainly — this doubles as the roadmap:

  • It is not a fuzzy-ranking search engine. The filter is a case-insensitive substring match over each item's label plus its optional keywords, preserving source order. cmdk and Bits UI ship real fuzzy scoring that reranks results by match quality; Rozie does not. For fuzzy ranking, pre-rank your items and feed the ordered list in — the query model and the select event give you the hook.
  • It is a flat list, not nested groups. Items carry an optional group field surfaced per-row (and in the option slot scope), but Rozie does not render sticky group headings automatically the way cmdk's Command.Group or Bits UI's Command.Group do. Render grouped headings yourself via the option slot.
  • No built-in async / loading state. There is no first-class "loading" affordance; drive it through your items array and the empty slot. (cmdk and Bits UI have explicit loading states.)
  • It does not own a global keyboard shortcut. Bind ⌘K / Ctrl-K yourself and call show() (or set the open model). cmdk, kbar, and ninja-keys variously help with this; Rozie stays unopinionated about how the palette is summoned.
  • It is self-contained, not composed from the listbox family. The results list is authored inline (scoped slots + roving nav), not by composing the published @rozie-ui/listbox package — cross-family composition of published leaves isn't expressible in the compiler today. The accessibility primitives are the same; the implementation is just not a dependency on another leaf.
  • @rozie-ui/command-palette is 0.1.0. The surface (8 props / 1 select event / a 4-verb handle / option · empty · footer slots / two-way open + query) is stable and gate-verified across all six targets, but it is far younger and less battle-tested than cmdk.

Try it

Pre-v1.0 — internal monorepo.