Getting Started

Get cmdk-engine running in your React project in under 5 minutes.

Installation

bash
npm install cmdk-engine cmdk react react-dom

Or with other package managers:

bash
bun add cmdk-engine cmdk
pnpm add cmdk-engine cmdk
yarn add cmdk-engine cmdk

Prefer Base UI? Install @base-ui/react instead of cmdk and import the palette from cmdk-engine/adapters/base-ui. See Adapters.

Requirements

  • React 18 or 19.
  • A UI adapter: cmdk ^1 for the cmdk adapter, or @base-ui/react ^1.1 for the Base UI adapter. Or build your own UI with the hooks.
  • Optional: react-router 6, 7 or 8 for the route scanner, and match-sorter 7 or 8 for the match-sorter search backend.
  • Node.js 20 or later, needed by the CLI only. The library runs in the browser and during SSR.

Support: the latest minor release gets fixes. While the version is 0.x, a minor release can change behavior; each such change is listed as a "Behavior change" in the changelog.

1. Add the Provider

Wrap your app with CommandEngineProvider, for example in a Providers component rendered once near the root. It initializes the command registry, search engine, and frecency tracker. Define config once, outside the component: a new object on every render rebuilds the engine.

Providers.tsx
import { CommandEngineProvider } from 'cmdk-engine/react'

const config = {
  synonyms: {
    billing: ['money', 'payment', 'credits'],
  },
  // Runs for commands with an href and no action. With a React Router
  // data router, use (href) => router.navigate(href) to skip the reload.
  onNavigate: (href: string) => window.location.assign(href),
  frecency: { showRecent: true },
}

export function Providers({ children }: { children: React.ReactNode }) {
  return <CommandEngineProvider config={config}>{children}</CommandEngineProvider>
}

2. Register Commands

Use useCommandRegister to register commands from any component under the provider. Commands are removed when the component unmounts, so register app-wide navigation in a layout that stays mounted, and page-specific commands in the page.

BillingPage.tsx
import { useCommandRegister } from 'cmdk-engine/react'
import { CreditCard } from 'lucide-react'

function BillingPage() {
  useCommandRegister([{
    id: 'billing-overview',
    label: 'Billing Overview',
    href: '/billing/overview',
    keywords: ['balance', 'credits'],
    group: 'Billing',
    icon: <CreditCard size={16} />,
  }])

  return <div>...</div>
}

3. Add the Command Palette

Use the pre-wired cmdk adapter or build your own UI with hooks.

CommandMenu.tsx
import { CommandPalette } from 'cmdk-engine/adapters/cmdk'

function CommandMenu() {
  return <CommandPalette dialog placeholder="Search commands..." />
}

4. Add Keyboard Shortcut

Call useCommandPaletteShortcut in a component inside the provider, such as the CommandMenu from step 3. Calling it in the component that renders the provider throws, because that component sits outside it.

CommandMenu.tsx
import { CommandPalette, useCommandPaletteShortcut } from 'cmdk-engine/adapters/cmdk'

function CommandMenu() {
  useCommandPaletteShortcut('k') // Cmd+K / Ctrl+K
  return <CommandPalette dialog placeholder="Search commands..." />
}

5. Style the Palette

CommandPalette ships no styles, so it looks unstyled until you add some. Copy the starter stylesheet (plain CSS or Tailwind) from the Styling page, then press Cmd+K (Ctrl+K on Windows and Linux).

Troubleshooting

... must be used within a <CommandEngineProvider>

A cmdk-engine hook or component ran outside the provider. The message starts with the name of the hook that failed, such as useCommandRegister, and lists the usual causes:

  • The hook is called in the component that renders CommandEngineProvider. Move it into a child component.
  • Two copies of cmdk-engine are installed (in a monorepo, or at mismatched versions), so the component and the provider use different contexts. Make sure one copy resolves.
  • A bundler or test alias maps cmdk-engine/react or an adapter path but not cmdk-engine. The entries import each other by package name, so map them all.

The dialog never opens

A palette with dialog opens only when something toggles it. Call useCommandPaletteShortcut() in a component inside the provider, or call toggle() from useCommandPalette().

Next.js: "Event handlers cannot be passed to Client Component props"

Put 'use client' at the top of the file that renders the provider. A Server Component cannot pass functions such as onNavigate to it.

Next Steps