CommandPalette
A search box in a dialog that finds and runs commands, pages and records by name. It speeds up people who use the app every day; everything in it also needs a visible path.
Installation
Set up Camaleo once (Installation), then import what you need. It renders on the client.
import { CommandPalette, CommandPaletteTrigger, CommandPaletteContent, CommandPaletteInput, CommandPaletteList, CommandPaletteGroup, CommandPaletteItem, CommandPaletteEmpty, CommandPaletteFooter } from "@camaleo/react";Anatomy
CommandPaletteContent renders the backdrop and the dialog in a portal. Focus stays in the search; the arrow keys move a highlight through the list with aria-activedescendant.
Examples
Every example is a real file you can copy, in the skin, mode and brand from the bar above. Each one starts closed: open it, type, use the arrows and Enter, then Esc. The palette doesn't filter on its own. Keep the query in state, render only the commands that match, and give CommandPaletteList a key={query}, so the arrow keys follow the new order.
Ctrl+K and command shortcuts
Press Ctrl+K anywhere on this page. The button stays as the visible way in, and each command shows its own shortcut so people learn it.
Groups: Navigation, Actions, Recent
Headings help people scan a long list. A group with no matches disappears with its heading, and the highlight scrolls into view.
Keywords and synonyms
Type "receipt" or "add user". People search with their own words, so match keywords too and say which one matched.
Empty state
Repeat what was searched and suggest what to try. The query stays in the box so it's easy to fix.
Results from the server
Type a name. While the request runs, the list has aria-busy and the footer says "Searching…"; a newer search makes the old one stale.
Pages inside the palette
"Change theme…" opens a page of options. Esc or Backspace in an empty search goes back one level before closing.
Counts and results announced
The footer count is a polite status, read when typing pauses. After a command runs, a status outside the palette says what happened.
A command you can't run
Keep it in the list with aria-disabled and say why. People find it, learn who can do it, and Enter does nothing.
In context
These are whole pieces of UI you can copy, in the skin you picked.
Guidelines
Creating and exporting have their own buttons. The palette is the faster way for people who know what they want.
Nobody browses a search box. If "Create invoice" isn't on the page, most people never find it.
"Send payment reminder" says what will happen, and the first word is what people type.
"Invoice" could open, create or delete one. A sentence gets cut off and is hard to match.
Props
onOpenChange to control the state.true traps focus and hides the page behind from screen readers. Keep the default.eventDetails.reason says why, for example escape-key.CommandPaletteContent, CommandPaletteList, CommandPaletteEmpty and CommandPaletteFooter take the props of their div; pass aria-label to the content and the list. CommandPaletteInput takes the props of an input, including value, onChange and aria-label. CommandPaletteTrigger takes render.
Each command is a CommandPaletteItem:
data-value, for your own filtering or tests. The palette does not filter by it.onSelect doesn't run. Say why in the label.onSelect runs the command, on a click or on Enter while the item is highlighted. It also takes the props of its div, like aria-disabled, aria-keyshortcuts and onMouseDown. CommandPaletteGroup takes label, its visible heading.
Styling hooks
Restyle it without forking: target the attributes, or set the tokens on a parent, a skin or the palette itself.
Attributes
Tokens it reads
Color
Shape & size
Type & motion
This component only
/* a taller list for the file finder */
.editor [data-slot="command-palette-list"] {
max-height: 24rem;
}Accessibility
- The dialog is modal and needs a name: pass aria-label to CommandPaletteContent, and another to CommandPaletteInput, in the page's language.
- The search is a combobox with aria-expanded and aria-controls pointing to the listbox. aria-activedescendant follows the highlight, so screen readers read each command as you move.
- Results change silently. Put the count in a role="status" inside the palette, and after a command runs, report the result in a status outside it: the palette is gone and focus is back on the trigger.
- Ctrl+K listens on the whole page. Leave shortcut on for one palette only and turn it off on the others, or one key press opens them all.