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.
On this page
Chats, actions and models in one field. Press the shortcut or click the trigger, then type “rlp”.
Installation
Usage
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
Server search
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:
nwcfinds New chat andrlpfinds Add rate limiting to the public API. keywordsmatch 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
emptyLimitcaps each one.
Props
| Prop | Type | Notes |
|---|---|---|
items | CommandPaletteItem[] | id, label, group, icon, shortcut, hint, keywords, disabled, keepOpen, onSelect |
groups | CommandPaletteGroup[] | Order and labels. emptyLimit and searchOnly per group |
open, defaultOpen, onOpenChange | Controlled or uncontrolled | |
query, defaultQuery, onQueryChange | Control the query for async sources | |
filter | boolean | Rank and filter locally. Default true |
loading | boolean | Spinner after 200 ms, Searching… while there are no rows |
hotkey | string | null | Default mod+k |
onSelect | (item) => void | Runs after the item's own onSelect |
placeholder | string | Default Search chats, actions and models… |
emptyHint | ReactNode | Under No results for … |
footer | ReactNode | false | Replaces the key hints, or hides them |
apple | boolean | Force Mac or PC key labels |
container | HTMLElement | Portal container |
Keyboard
| Key | Action |
|---|---|
Cmd+K, Ctrl+K | Open or close from anywhere |
↓, ↑, Ctrl+N, Ctrl+P | Move the highlight. Wraps around |
Home, End | First or last result |
Enter | Run the highlighted item |
Esc | Close and return focus |
Accessibility
- The field is a
comboboxwitharia-activedescendant, so focus never leaves it while the highlight moves through thelistbox. - Groups are
role=groupwith their label. Shortcuts are exposed witharia-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.