Skip to content
ShellCommand paletteMenu

Command palette

Cmd/Ctrl+K: one field that finds chats, runs actions and switches models, with fuzzy matching, groups, shortcut hints and no open animation from the keyboard.

Chats, actions and models in one field. Press the shortcut or click the trigger, then type “rlp”.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/command-palette.json

Usage

tsx
import { CommandPalette } from "@/components/ui/chatcn/command-palette"

<CommandPalette
  groups={[
    { id: "chats", label: "Chats", emptyLimit: 5 },
    { id: "actions", label: "Actions" },
    { id: "models", label: "Models" },
  ]}
  items={[
    { id: "c1", label: "Fix the slow runs page", group: "chats", hint: "Today", onSelect: () => open("c1") },
    { id: "new", label: "New chat", group: "actions", shortcut: "mod+shift+o", onSelect: newChat },
    { id: "m1", label: "Claude Sonnet 4.5", group: "models", onSelect: () => setModel("sonnet") },
  ]}
/>

The palette binds mod+k itself: Cmd on a Mac, Ctrl elsewhere. Pass hotkey={null} when your app owns the shortcut, and control it with open and onOpenChange. CommandPaletteTrigger is a quiet search-shaped button that shows the shortcut.

Examples

Control the query, fetch on onQueryChange and pass filter={false} so the palette shows the server's ranking as is. loading shows a spinner after 200 ms, so fast responses never flash one.

Results from a slow server: the spinner waits 200 ms, then no results with a hint.

Matching

  • A substring that starts a word ranks first, then any substring, then every word of the query in any order, then the letters in order: nwc finds New chat and rlp finds Add rate limiting to the public API.
  • keywords match without being shown, for synonyms like preferences for Open settings.
  • While searching, the group with the best match moves to the top. With an empty query, groups keep their order and emptyLimit caps each one.

Props

PropTypeNotes
itemsCommandPaletteItem[]id, label, group, icon, shortcut, hint, keywords, disabled, keepOpen, onSelect
groupsCommandPaletteGroup[]Order and labels. emptyLimit and searchOnly per group
open, defaultOpen, onOpenChangeControlled or uncontrolled
query, defaultQuery, onQueryChangeControl the query for async sources
filterbooleanRank and filter locally. Default true
loadingbooleanSpinner after 200 ms, Searching… while there are no rows
hotkeystring | nullDefault mod+k
onSelect(item) => voidRuns after the item's own onSelect
placeholderstringDefault Search chats, actions and models…
emptyHintReactNodeUnder No results for …
footerReactNode | falseReplaces the key hints, or hides them
applebooleanForce Mac or PC key labels
containerHTMLElementPortal container

Keyboard

KeyAction
Cmd+K, Ctrl+KOpen or close from anywhere
↓, ↑, Ctrl+N, Ctrl+PMove the highlight. Wraps around
Home, EndFirst or last result
EnterRun the highlighted item
EscClose and return focus

Accessibility

  • The field is a combobox with aria-activedescendant, so focus never leaves it while the highlight moves through the listbox.
  • Groups are role=group with their label. Shortcuts are exposed with aria-keyshortcuts; the visual keys are hidden from screen readers.
  • The result count is announced politely once typing pauses, not on every keystroke.
  • Rows are 44 px on touch screens, and the key hints hide there.

Motion

  • Opened or closed from the keyboard, nothing animates. The palette is used dozens of times a day, and a shortcut should feel instant.
  • Opened with a pointer, it fades in over 150 ms and out over 110 ms. Opacity only, no scale, no slide.
  • The highlight moves instantly. Hovering moves it only when the pointer itself moves, never when the list scrolls under a still pointer.
  • The list height follows the results over 150 ms so the panel never jumps. Reduced motion snaps it.