CommandPalette
Everything an application can do, behind one field. The shape a keyboard-first product takes once it has more actions than a menu bar can hold: a reader types what they want instead of remembering where it was put.
import { CommandPalette } from 'neba';
<CommandPalette
items={[
{ value: 'deploy', label: 'Deploy production', group: 'Actions', onSelect: deploy },
{ value: 'logs', label: 'Go to logs', group: 'Navigate' }
]}
/>;Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items * | readonly CommandItem[] | — | Everything the palette can do |
| open | boolean | — | Whether the palette is open. With onOpenChange it makes it controlled |
| defaultOpen | boolean | false | Whether it starts open, uncontrolled |
| onOpenChange | (open: boolean) => void | — | Fired whenever it opens or closes |
| onSelect | (item: CommandItem) => void | — | Called when a command is run, after its own onSelect. The palette closes either way |
| shortcut | string | false | 'Mod+K' | The keystroke that opens the palette, bound on the window. Mod is Command on a Mac and Control everywhere else; false binds nothing |
| width | number | string | — | How wide the sheet may get. Numbers are pixels |
| maxHeight | number | string | 320 | How tall the list may get before it scrolls |
| sizeshared | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | The type scale, the height of the field and how wide the sheet may get |
| colorshared | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | The role the highlighted row and the sheet edge carry |
| densityshared | 'default' | 'compact' | 'default' | Tightens the rows and nothing else |
| locale | string | — | BCP 47 tag the placeholder, the empty line and the dialog's name are written in |
| placeholder | string | — | The placeholder in the field |
| emptyMessage | ReactNode | — | The line where the rows would be, when nothing matched |
| label | string | — | The accessible name of the dialog, which has no visible title |
| className | string | — | Class names for the sheet |
| classNames | NebaSlots<'backdrop' | 'viewport' | 'input' | 'list' | 'group' | 'item' | 'empty'> | — | Class names for the parts behind the root. The root itself is className, so there is no root key |
CommandItem
| Prop | Type | Default | Description |
|---|---|---|---|
| value * | string | — | What identifies the command |
| label * | string | — | What the row says, and what the query is matched against |
| description | ReactNode | — | A second line under it: where the command goes, or what it changes |
| icon | ReactNode | — | A glyph before the label |
| shortcut | string | — | The keystroke that does the same thing, written the way Shortcut writes them. The palette does not bind it |
| group | string | — | The heading this command sits under. A heading is drawn each time the group changes, so a group has to be listed together |
| keywords | readonly string[] | — | Extra words the query is matched against but that are never drawn |
| disabled | boolean | false | In the list but not runnable |
| onSelect | () => void | — | What running it does |
A Menu is a short list in one place, with every row visible before you look for it. A Combobox returns a value. This returns an action that runs.
Examples
items · group
Commands are drawn in the order they are given, and a heading is drawn each time group changes, so a group's commands have to be listed together. icon and shortcut fill in the two ends of a row, and description a second line under the label.
keywords
Extra words the query is matched against but that are never drawn: the name somebody else's product gives the same command, an abbreviation, the word a reader would have searched for. Roll back found by typing undo is what makes a palette worth opening twice.
shortcut
The keystroke that opens the palette, bound on the window. Mod is Command on a Mac and Control everywhere else: the same spelling Shortcut draws, read rather than written. false binds nothing, for an application that owns its own keyboard.
onSelect
Each command may carry its own onSelect; the palette's runs after it, with the item. The palette closes either way, and the query is dropped on the way out.
size
size sets the type scale, the height of the field and how wide the sheet may get. width and maxHeight override the last two on their own.
className · classNames
className lands on the sheet: the panel the search field and the rows sit on. Everything around and inside it is reached through classNames.
<CommandPalette
items={commands}
className="max-w-2xl"
classNames={{ backdrop: 'backdrop-blur-none', item: 'rounded-none' }}
/>The slots are backdrop, viewport, input, list, group, item and empty. backdrop and viewport render outside the sheet, so nothing written against it finds them; group is one heading between the rows, not the rows under it. See prop conventions for how a class name you pass resolves against the component's own.
Accessibility
- The sheet is a modal dialog named by
label, which has no visible title of its own. Focus moves into the field as it opens and back to wherever the reader was as it closes. - The field is a
comboboxover alistbox, with the highlighted row reported througharia-activedescendant; the pointer and the arrow keys move the same highlight, so Enter never runs a row other than the marked one. - Escape closes it.
- A palette is never the only way to a command. Everything in it has to be reachable some other way: a reader who does not know it exists gets no other showing.