Skip to content

State Machine

ABCrimson edited this page Mar 11, 2026 · 2 revisions

State Machine

The core of modern-cmdk is a pure TypeScript state machine that manages all command palette state.

Creating a Machine

import { createCommandMachine, itemId } from 'modern-cmdk';

// `using` ensures automatic cleanup
using machine = createCommandMachine({
  items: [
    { id: itemId('copy'), value: 'Copy to Clipboard', shortcut: 'Mod+C' },
    { id: itemId('paste'), value: 'Paste', shortcut: 'Mod+V' },
    { id: itemId('settings'), value: 'Open Settings', keywords: ['preferences'] },
  ],
  loop: true,        // Arrow keys wrap around
  frecency: { enabled: true },
});

CommandState

Every state change produces an immutable CommandState snapshot:

interface CommandState {
  readonly search: string;              // Current search query
  readonly activeId: ItemId | null;     // Currently highlighted item
  readonly filteredIds: readonly ItemId[];  // Items passing the filter
  readonly groupedIds: ReadonlyMap<GroupId, readonly ItemId[]>;
  readonly filteredCount: number;       // Shortcut for filteredIds.length
  readonly loading: boolean;            // Async items loading
  readonly page: string;               // Current page
  readonly pageStack: readonly string[];  // Page history
}

Subscribing to Changes

// useSyncExternalStore-compatible (returns unsubscribe function)
const unsubscribe = machine.subscribe(() => {
  const state = machine.getState();
  console.log('Active item:', state.activeId);
});

// Or use the Disposable pattern
using subscription = machine.subscribeState((state) => {
  console.log('Filtered count:', state.filteredCount);
});

Sending Events

// Search
machine.send({ type: 'SEARCH_CHANGE', query: 'settings' });

// Navigation
machine.send({ type: 'NAVIGATE', direction: 'next' });
machine.send({ type: 'NAVIGATE', direction: 'prev' });
machine.send({ type: 'NAVIGATE', direction: 'first' });
machine.send({ type: 'NAVIGATE', direction: 'last' });

// Selection
machine.send({ type: 'ITEM_SELECT', id: itemId('copy') });

// Dialog
machine.send({ type: 'OPEN' });
machine.send({ type: 'CLOSE' });
machine.send({ type: 'TOGGLE' });

// Pages
machine.send({ type: 'PAGE_PUSH', page: 'settings' });
machine.send({ type: 'PAGE_POP' });

// Dynamic items
machine.send({ type: 'REGISTER_ITEM', item: { id: itemId('new'), value: 'New Item' } });
machine.send({ type: 'UNREGISTER_ITEM', id: itemId('new') });

Resource Cleanup

The machine implements Disposable for automatic cleanup:

{
  using machine = createCommandMachine({ items });
  // ... use machine
} // machine[Symbol.dispose]() called automatically — all listeners removed

Clone this wiki locally