Command
Command palette / fuzzy search menu.
Usage
The command-palette pattern. Bits owns the filtering, the scoring and arrow-key navigation, and it keeps focus in the Input while the highlight moves — so the user types and navigates without ever leaving the field. To open it in a modal, wrap the whole thing in a Dialog.
Try typing trash — it matches Delete, which does not contain the word.
Viewport is required
Command.Viewport goes inside List and wraps
the content. It is not optional.
Bits takes the Input's aria-controls from the Viewport's id, and uses
the Viewport as the insertion element when sorting filtered items. Omit it and the combobox is
invalid ARIA — axe reports aria-required-attr, because a role="combobox" with aria-expanded must say what
it controls.
This is not hypothetical: our own a11y suite caught a Command built without one, which is why it is called out here rather than left as a detail in the API table.
Naming the field and the list
There are two names to set, and the prop names do not make that obvious:
labelonRootnames the search field. It renders a visually hidden<label>that the Input references.aria-labelonListnames the list. Bits defaults it to"Suggestions...", which says nothing about what the suggestions are — override it.
We deliberately leave Bits' default in place rather than silently substituting our own, so the behaviour matches their documentation. Setting both is on you.
Keywords and filtering
value is what the query matches against. Add keywords for the words a user would actually type but that are not in
your label — trash on a Delete item, preferences on Settings. This is the difference between a palette people
use and one they abandon because it never finds anything.
A group whose items are all filtered out is hidden along with its heading, so you never get a
heading over nothing. Set shouldFilter=false when you filter server-side
and render only what you want shown.
Async results
Use Loading while a request is in flight, not Empty. "No results" and "still loading" are different answers, and
showing the first one during a fetch tells the user to stop typing when they should wait.
Always render one or the other — a palette that goes silently blank reads as broken.
Announcing results
Bits filters the list and announces nothing. Without Command.Status, a screen reader user types and the list shrinks in
silence — they never learn whether they have forty matches or none.
<Command.Root label="Command palette">
<Command.Input bind:value={search} />
<!-- Once, anywhere inside Root. Pass a label in your language. -->
<Command.Status label={(n) => (n === 1 ? '1 resultado' : `${n} resultados`)} />
<Command.List aria-label="Commands">
<Command.Viewport>…</Command.Viewport>
</Command.List>
</Command.Root>It is a visually hidden role="status" region. Hidden because the count is
already on screen — the results are right there — so duplicating it visibly would be noise for everyone
else.
Pass label. The default is English, and a count is exactly
the kind of string that needs your language and plural rules.
delay is not a performance tweak. Without it, typing "button" fires six
announcements — "48 results", "12 results", "4 results" — and the user hears a torrent instead of
an answer. Each keystroke restarts the wait, so they get one answer once they stop typing. The default
is 500ms.
It stays silent until the search is non-empty: the count of an unfiltered list is not news when a palette opens, and it falls silent again if the search is cleared.
Combobox needs the same thing and gets no component,
deliberately — Bits does not filter there, you do, so you already know the count. Render
your own region with it. There is nothing this library can add that you do not already have.
Props
Command.Root
| Prop | Type | Default |
|---|---|---|
value The highlighted item's value. Bindable. | string | '' |
class Extra classes merged onto the root. | string | — |
child bits-ui | Snippet<[{ props: Record<string, unknown>; }]> | undefined | — |
children bits-ui | Snippet<[]> | undefined | — |
columns bits-ui The number of columns in a grid layout. | number | null | undefined | null |
disableInitialScroll bits-ui Whether to disable scrolling the selected item into view on initial mount. When `true`, prevents automatic scrolling when the command menu first renders and selects its first item, but still allows scrolling on subsequent selections. | boolean | undefined | false |
disablePointerSelection bits-ui Optionally set to `true` to disable selection via pointer events. | boolean | undefined | — |
filter bits-ui A custom filter function for whether each command item should match the query. It should return a number between `0` and `1`, with `1` being a perfect match, and `0` being no match, resulting in the item being hidden entirely. By default, it will use the `computeCommandScore` function exported by this package to compute the score. | ((value: string, search: string, keywords?: string[] | undefined) => number) | undefined | — |
id bits-ui | string | undefined | — |
label bits-ui An accessible label for the command menu. Not visible & only used for screen readers. | string | undefined | — |
loop bits-ui Optionally set to `true` to enable looping through the items when the user reaches the end of the list using the keyboard. | boolean | undefined | — |
onStateChange bits-ui A function that is called when the command state changes. | ((state: Readonly<CommandState>) => void) | undefined | — |
onValueChange bits-ui A function that is called when the selected command menu item changes. It receives the new value as an argument. | ((value: string) => void) | undefined | — |
ref bits-ui | HTMLElement | null | undefined | — |
shouldFilter bits-ui Optionally set to `false` to turn off the automatic filtering and sorting. If `false`, you must conditionally render valid items yourself. | boolean | undefined | — |
vimBindings bits-ui Set this prop to `false` to disable the option to use ctrl+n/j/p/k (vim style) navigation. | boolean | undefined | true |
Command.Input
| Prop | Type | Default |
|---|---|---|
value The search query. Bindable. | string | '' |
class Extra classes merged onto the input. | string | — |
child bits-ui | Snippet<[{ props: Record<string, unknown>; }]> | undefined | — |
children bits-ui | Snippet<[]> | undefined | — |
id bits-ui | string | undefined | — |
ref bits-ui | HTMLElement | null | undefined | — |
Command.List
| Prop | Type | Default |
|---|---|---|
class Extra classes merged onto the list. | string | — |
child bits-ui | Snippet<[{ props: Record<string, unknown>; }]> | undefined | — |
children bits-ui | Snippet<[]> | undefined | — |
id bits-ui | string | undefined | — |
ref bits-ui | HTMLElement | null | undefined | — |
Command.Item
| Prop | Type | Default |
|---|---|---|
class Extra classes merged onto the item. | string | — |
child bits-ui | Snippet<[{ props: Record<string, unknown>; }]> | undefined | — |
children bits-ui | Snippet<[]> | undefined | — |
disabled bits-ui Whether the item is disabled. | boolean | undefined | false |
forceMount bits-ui Whether to always mount the item regardless of filtering logic. | boolean | undefined | — |
id bits-ui | string | undefined | — |
keywords bits-ui A list of keywords that will be used to filter the item. | string[] | undefined | — |
onSelect bits-ui A callback that is fired when the item is selected, either via click or keyboard selection. | (() => void) | undefined | — |
ref bits-ui | HTMLElement | null | undefined | — |
value bits-ui A unique value for this item that will be used when filtering and ranking the items. If not provided, an attempt will be made to use the `textContent` of the item. If the `textContent` is dynamic, you will need to provide a stable unique value for the item. | string | undefined | — |
Command.Status
| Prop | Type | Default |
|---|---|---|
label Builds the announced text from the result count. Pass your own. The default is English, and a count is exactly the kind of string that needs the app's language and plural rules — `1 result` versus `1 resultado` versus languages with more than two plural forms. | (count: number) => string | (count: number) =>
count === 0 ? 'No results' : count === 1 ? '1 result' : `${count} results` |
delay Milliseconds to wait after the last keystroke before announcing. Not a performance tweak. Without it, typing "button" fires six announcements — "48 results", "12 results", "4 results" — and a screen reader user hears a torrent instead of an answer. Long enough to let typing settle, short enough to arrive while the search is still the subject. | number | 500 |
class Extra classes merged onto the region. | string | — |
Command.Item
Input, List, Group, GroupHeading, Empty, Separator and Loading take class plus their native
attributes. Viewport, GroupItems and LinkItem are re-exported from Bits unchanged.