Command
A command palette with fuzzy scoring, keyboard navigation, groups, empty/loading states, and vim-style keybindings. Supports grid mode (emoji picker), external filtering, and a Dialog composition variant.
Anatomy
<Command.Provider bind:value={selected}>
<Command.Input placeholder="Type a command…" />
<Command.List>
<Command.Empty>No results.</Command.Empty>
<Command.Group value="File">
<Command.GroupHeading>File</Command.GroupHeading>
<Command.GroupItems>
<Command.Item value="file.new" onSelect={run}>New File</Command.Item>
<Command.Item value="file.open" onSelect={run}>Open</Command.Item>
</Command.GroupItems>
</Command.Group>
<Command.Separator />
<Command.LinkItem value="docs" href="https://svelte.dev">Docs</Command.LinkItem>
</Command.List>
</Command.Provider>
Parts
| Part |
Element |
Description |
Provider |
<div> |
Root context. Manages value, search, filtering, and navigation. |
Input |
<input> |
Combobox input. Drives search filtering and keyboard navigation. |
List |
<div> |
Listbox container for items, groups, and empty state. |
Viewport |
<div> |
Measures child height and exposes --command-list-height. |
Item |
<div> |
Selectable option. Only renders when visible (passes filter). |
LinkItem |
<a> |
Same as Item but renders an anchor for navigation. |
Group |
<div> |
Groups related items. Always renders; sets data-empty for CSS. |
GroupHeading |
<div> |
Group label, linked via aria-labelledby. |
GroupItems |
<div> |
Wrapper for items within a group. |
Empty |
<div> |
Renders when no items survive the filter. |
Loading |
<div> |
Optional progress bar for async states. |
Separator |
<span> |
Visual divider between groups or sections. |
Dialog |
— |
Composition: wraps Dialog + Command for modal usage. |
ARIA
| Part |
Attribute |
Value |
| Input |
role |
combobox |
| Input |
aria-autocomplete |
list |
| Input |
aria-expanded |
true |
| Input |
aria-controls |
ID of List |
| Input |
aria-activedescendant |
ID of highlighted Item |
| List |
role |
listbox |
| List |
aria-label |
Translated label |
| Item |
role |
option |
| Item |
aria-selected |
true | false |
| Item |
aria-disabled |
true | false |
| LinkItem |
role |
option |
| LinkItem |
aria-selected |
true | false |
| LinkItem |
aria-disabled |
true | false |
| Group |
role |
group |
| Group |
aria-labelledby |
ID of GroupHeading |
| GroupHeading |
aria-hidden |
true |
| GroupItems |
role |
presentation |
| Empty |
role |
status |
| Empty |
aria-live |
polite |
| Loading |
role |
progressbar |
| Loading |
aria-valuemin |
0 |
| Loading |
aria-valuemax |
100 |
| Loading |
aria-valuenow |
0–100 or omitted (indeterminate) |
| Separator |
role |
separator |
| Separator |
aria-orientation |
horizontal |
Data Attributes
| Part |
Attribute |
Values |
| Provider |
data-command |
Always present |
| Input |
data-command-input |
Always present |
| List |
data-command-list |
Always present |
| Viewport |
data-command-viewport |
Always present |
| Item |
data-command-item |
Always present |
| Item |
data-selected |
Present when highlighted |
| Item |
data-disabled |
Present when disabled |
| Item |
data-value |
Item's value string |
| LinkItem |
data-command-link-item |
Always present |
| LinkItem |
data-selected |
Present when highlighted |
| LinkItem |
data-disabled |
Present when disabled |
| LinkItem |
data-value |
Item's value string |
| Group |
data-command-group |
Always present |
| Group |
data-empty |
Present when no items visible |
| GroupHeading |
data-command-group-heading |
Always present |
| GroupItems |
data-command-group-items |
Always present |
| Empty |
data-command-empty |
Always present |
| Loading |
data-command-loading |
Always present |
| Loading |
data-value |
Progress value |
| Separator |
data-command-separator |
Always present |
CSS Variables
| Variable |
Part |
Description |
--command-list-height |
Viewport |
Measured height of child element |
Keyboard
| Key |
Action |
ArrowDown |
Move highlight down (or by columns) |
ArrowUp |
Move highlight up (or by columns) |
ArrowRight |
Move highlight right (grid mode) |
ArrowLeft |
Move highlight left (grid mode) |
Home |
Highlight first item |
End |
Highlight last item |
Enter |
Select highlighted item |
Ctrl+N / Ctrl+J |
Next item (vim bindings) |
Ctrl+P / Ctrl+K |
Previous item (vim bindings) |
Horizontal arrow keys are direction-inverted in RTL mode.
Usage
Basic command palette
<script>
import { Command } from '$soma/components';
let selected = $state('');
</script>
<Command.Provider bind:value={selected}>
<Command.Input placeholder="Type a command…" />
<Command.List>
<Command.Empty>No results.</Command.Empty>
<Command.Item value="copy" onSelect={(v) => console.log(v)}>Copy</Command.Item>
<Command.Item value="paste">Paste</Command.Item>
</Command.List>
</Command.Provider>
With groups
<Command.List>
<Command.Group value="edit">
<Command.GroupHeading>Edit</Command.GroupHeading>
<Command.GroupItems>
<Command.Item value="cut">Cut</Command.Item>
<Command.Item value="copy">Copy</Command.Item>
</Command.GroupItems>
</Command.Group>
</Command.List>
Grid mode (emoji picker)
<Command.Provider columns={10} label="Pick an emoji">
<Command.Input placeholder="Filter…" />
<Command.List>
{#each emojis as e (e)}
<Command.Item value={e}>{e}</Command.Item>
{/each}
</Command.List>
</Command.Provider>
External filter
<Command.Provider shouldFilter={false}>
<!-- Items are always visible; consumer controls what's shown -->
</Command.Provider>
Dialog composition
<script>
let open = $state(false);
</script>
<button onclick={() => (open = true)}>Open</button>
<Command.Dialog bind:open>
<Command.Input placeholder="Search…" />
<Command.List>
<Command.Item value="action">Action</Command.Item>
</Command.List>
</Command.Dialog>
Imperative API via snippet props
<Command.Provider bind:value={apiValue} bind:search={apiSearch}>
{#snippet children({ next, prev, select, isEmpty, visible })}
<Command.Input />
<Command.List>
<Command.Item value="a">Item A</Command.Item>
</Command.List>
<div>
<button onclick={() => prev()}>Prev</button>
<button onclick={() => next()}>Next</button>
<span>{isEmpty ? 'no match' : `${visible.length} visible`}</span>
</div>
{/snippet}
</Command.Provider>