Skip to content

CommandPalette

<CommandPalette> is the app-wide ⌘K menu. Wrap the app in CommandPaletteProvider once; anywhere inside, call useRegisterCommand to add an entry. The palette supports groups, shortcut hints, and the global hotkey is suppressed inside editable elements by default.

import from "@na/ui/components/CommandPalette" ▶ Open in Storybook packages/ui/src/components/CommandPalette.tsx

When to use

  • Linear-style ⌘K menu for power-user actions and navigation.
  • Workspace switchers, “create” shortcuts, in-app search of named objects.

Don’t use CommandPalette for: per-field option pickers (Combobox), navigation between top-level pages exclusively (a sidebar is better discovered), or actions that require parameters (open a Dialog on activation).

Basic

Press ⌘K (or Ctrl+K) to open the palette.

import {
CommandPaletteProvider,
useRegisterCommand,
} from '@na/ui/components/CommandPalette';
// Wrap once at the app root
<CommandPaletteProvider hotkey="mod+k" groupOrder={['Actions', 'Navigate']}>
<App />
</CommandPaletteProvider>
// Inside any component
function CreateDealAction() {
const router = useRouter();
useRegisterCommand({
id: 'create-deal',
label: 'Create deal',
icon: <IconPlus />,
shortcut: '⌘D',
group: 'Actions',
run: () => router.push('/deals/new'),
});
return null;
}

API

CommandPaletteProvider

Prop Type Default Description
hotkey string "mod+k" Hotkey to toggle. Use `mod` for Cmd-or-Ctrl. Combine with `+`: `"mod+shift+p"`.
suppressInEditableContext boolean true Ignore the hotkey when the user is typing in an input / textarea / contentEditable.
title string "Command Palette" sr-only dialog title.
description string "Search for a command to run." sr-only dialog description.
groupOrder string[] Group display order. Groups not listed appear after, in registration order.
defaultGroup string "Actions" Group name when an entry doesn't specify one.
emptyState ReactNode "No results." Empty-state copy when no entries match.

CommandEntry

Prop Type Default Description
id * string Unique entry identifier.
label * ReactNode Visible label.
icon ReactNode Leading icon.
shortcut ReactNode Right-aligned keyboard hint. Informational; not bound by this primitive.
group string defaultGroup Group name.
keywords string[] Search-time keywords. Defaults to the label when string.
run * () => void Fires when the entry is activated.
hidden boolean false Hide without unregistering.

useCommandPalette() / useRegisterCommand(entry, deps?)

useCommandPalette() returns { register, unregister, open, setOpen, toggle }. useRegisterCommand(entry, deps) is a thin hook that registers on mount and cleans up on unmount.

Design guidelines

✓ Do

  • Group entries by purpose. "Actions", "Navigate", "Help" are the canonical three.
  • Pair every shortcut hint with a real binding elsewhere. Hints without bindings train users to mistrust shortcuts.
  • Use keywords for entries whose label hides the verb ("Settings → Billing" should match "billing").

✗ Don't

  • Register entries that require parameters. Open a Dialog after activation instead.
  • Override the suppress-in-editable behavior — users will hit ⌘K mid-typing and lose context.
  • Show 100+ entries. The palette is a power-user shortcut, not a sitemap.

Accessibility

  • The palette opens a Radix Dialog — focus trapped, Esc closes.
  • Inner Command provides full ARIA listbox semantics + type-ahead.
  • The hotkey is suppressed inside editable elements by default (configurable).
  • Command — Underlying primitive (cmdk).
  • Combobox — Per-field searchable picker.

▶ Open CommandPalette stories in Storybook