Command
A searchable, ranked list of actions run from the keyboard: inline, in a popover, or as a command palette.
Basic
Type to filter and rank. Arrows move the highlight while focus stays in the input; Enter or a click runs the item.
Code
<Command bordered>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem onSelect={(v) => run(v)}><Icon name="calendar" /> Calendar</CommandItem>
<CommandItem><Icon name="search" /> Search Emoji</CommandItem>
<CommandItem disabled><Icon name="plus" /> Calculator</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem><Icon name="user" /> Profile <CommandShortcut>⌘P</CommandShortcut></CommandItem>
<CommandItem><Icon name="mail" /> Billing <CommandShortcut>⌘B</CommandShortcut></CommandItem>
<CommandItem><Icon name="settings" /> Settings <CommandShortcut>⌘S</CommandShortcut></CommandItem>
</CommandGroup>
</CommandList>
</Command>Usage
import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator, CommandShortcut, CommandDialog } from "@elements/ui/command";
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem onSelect={() => go("/calendar")}><Icon name="calendar" /> Calendar</CommandItem>
<CommandItem><Icon name="search" /> Search Emoji</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Settings">
<CommandItem>Profile <CommandShortcut>⌘P</CommandShortcut></CommandItem>
</CommandGroup>
</CommandList>
</Command>
<!-- a palette on Cmd+K -->
<CommandDialog open={state.palette} shortcut="k">...same parts...</CommandDialog>Examples
Dialog
CommandDialog is a palette in a modal dialog. shortcut="k" toggles it with Cmd+K or Ctrl+K from anywhere.
Press ⌘ K, or
Code
<CommandDialog open={state.palette} shortcut="k" label="Global command menu">
<Button slot="trigger">Open menu</Button>
<CommandInput placeholder="Search for a command to run..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Suggestions">
<CommandItem onSelect={() => state.palette = false}>Calendar</CommandItem>
...
</CommandGroup>
</CommandList>
</CommandDialog>Ranking and keywords
Exact and prefix matches rank above word starts, substrings and scattered letters. keywords add aliases. An unbound value picks the item highlighted at first.
Code
<Command value="codemirror" bordered>
<CommandInput placeholder="Search packages..." />
<CommandList>
<CommandItem e:for={p of PACKAGES} value={p}>{p}</CommandItem>
<CommandItem value="settings" keywords={["preferences", "options"]}>Settings</CommandItem>
</CommandList>
</Command>Sub-items while searching
Bind search and show some items only while there is a query, so a search reaches options a parent item would open.
Code
<Command search={state.search}>
<CommandInput placeholder="Search settings..." />
<CommandList>
<CommandItem>Change theme...</CommandItem>
<CommandItem e:if={state.search !== ""}>Change theme to dark</CommandItem>
<CommandItem e:if={state.search !== ""}>Change theme to light</CommandItem>
</CommandList>
</Command>Controlled and scrollable
Bind search and value to set the query and the highlight from code. loop wraps the arrows. A long list scrolls and keeps the highlight in view.
Code
<Command value={state.value} search={state.search} loop bordered>
...
</Command>
<Button onclick={() => state.search = "toggle"}>Search "toggle"</Button>
<Button onclick={() => state.value = "Run tests"}>Highlight "Run tests"</Button>Async
filter={null} leaves matching to you. Show CommandLoading while results load; CommandEmpty shows when there are none.
Code
<Command filter={null} search={state.query} bordered
onSearchChange={(q) => find(state, q)}>
<CommandInput placeholder="Search npm..." />
<CommandList>
<CommandLoading e:if={state.loading} progress={state.progress}>Fetching packages...</CommandLoading>
<CommandEmpty>No packages.</CommandEmpty>
<CommandItem e:for={p of state.results} value={p}>{p}</CommandItem>
</CommandList>
</Command>In a popover
A searchable picker: Command inside a Popover, closed when an item is chosen.
Code
<Popover open={state.open} size="auto" class="p-0">
<Button slot="trigger">{label(state.framework) || "Select framework..."}</Button>
<Command>
<CommandInput placeholder="Search framework..." />
<CommandList>
<CommandEmpty>No framework found.</CommandEmpty>
<CommandItem e:for={f of FRAMEWORKS} value={f.label}
onSelect={() => pick(state, f.value)}>
{f.label}
<Icon name="check" class={state.framework === f.value ? "" : "invisible"} />
</CommandItem>
</CommandList>
</Command>
</Popover>Always shown
forceMount keeps an item or a group whatever the search; alwaysRender keeps a separator while searching.
Code
<Command bordered>
<CommandInput placeholder="Search docs..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Docs">
<CommandItem>Installation</CommandItem>
<CommandItem>Routing</CommandItem>
</CommandGroup>
<CommandSeparator alwaysRender />
<CommandGroup heading="Help" value="help-links" forceMount>
<CommandItem forceMount>Ask the community</CommandItem>
</CommandGroup>
</CommandList>
</Command>Keyboard highlight, custom filter
disablePointerSelection leaves the highlight to the keyboard, and vimBindings={false} leaves Ctrl+N, J, P and K to the browser. filter takes (value, search, keywords) and returns a rank; this one keeps only items that start with the search.
Code
<Command disablePointerSelection vimBindings={false} filter={startsWith} bordered>
<CommandInput placeholder="Starts with..." />
<CommandList>...</CommandList>
</Command>
// value, search, keywords => 0 hides, higher ranks first
function startsWith(value: string, search: string, keywords: string[]): number {
let s = search.trim().toLowerCase();
return [value, ...keywords].some((x) => x.toLowerCase().startsWith(s)) ? 1 : 0;
}Pages
An item opens a page of more items. Escape, or Backspace on an empty search, goes back.
Code
<Command search={state.search} onKeyDown={(e) => back(state, e)} bordered>
<CommandInput placeholder={state.pages.length ? "Search teams..." : "What do you need?"} />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup e:if={state.pages.length === 0} heading="Actions">
<CommandItem onSelect={() => open(state, "teams")}>Join a team...</CommandItem>
<CommandItem>Search projects</CommandItem>
</CommandGroup>
<CommandGroup e:if={state.pages.at(-1) === "teams"} heading="Teams">
<CommandItem>Design</CommandItem>
<CommandItem>Engineering</CommandItem>
</CommandGroup>
</CommandList>
</Command>
// Escape, or Backspace on an empty search, goes back a page.
function back(state, e: KeyboardEvent): void {
if (state.pages.length && (e.key === "Escape" || (e.key === "Backspace" && !state.search))) {
e.preventDefault();
state.pages = state.pages.slice(0, -1);
}
}RTL
In a right-to-left region the icon, text and shortcuts mirror.
Code
<div dir="rtl">
<Command bordered>
<CommandInput placeholder="اكتب أمرا أو ابحث..." />
...
</Command>
</div>