Command
A command palette (⌘K to invoke, hotkey configurable) — search filtering, score-based ranking, keyboard selection, Enter to execute, nested sub-pages, recent history and multi-select batch run. open is controlled: it can be set externally, and the configurable global shortcut or Esc closes it (each open/close fires one oas-open-change).
Basic usage
Basic usage (open with ⌘J / Ctrl+J)
Controlled open
The open attribute is externally controlled: an external button sets open to open the palette; it closes via Esc / backdrop click / selecting a command (the component removes open; the host syncs state through oas-open-change).
When open, the backdrop covers the full screen, so no external "close" button is provided — use Esc / click the backdrop / select a command to close.
Externally controlled open (sync via oas-open-change)
Groups and empty state
Group titles render from the group field; an empty state shows when nothing matches (plain "No matching commands" when the query is empty, or "No commands found for "word"" with a search term).
Groups and empty state
Icons · descriptions · shortcut labels
Item fields icon (SVG path d or a full <svg> markup), description (secondary line) and shortcut (right-aligned kbd with meta/ctrl/shift/alt symbol mapping).
Icon + description + shortcut
Configurable invocation shortcut
The hotkey attribute configures the combo (mod/meta/ctrl/alt/shift, comma-separated for multiple); false disables the built-in listener. Default is mod+k (⌘K / Ctrl+K).
Custom shortcut (ctrl+shift+p) and disabled
Match highlighting
While searching, the matched characters in the label are wrapped in <mark> — instant feedback as you type.
Search highlight
External filtering (should-filter=false)
should-filter="false" disables built-in filtering and ranking — filtering is fully delegated to the host: the component fires oas-input as you type, and the host requests an async data source and writes back items. This demo simulates a 600ms server search; when there are no results it returns a forceMount "create" entry (rendered regardless of filtering).
should-filter=false + async data source
Empty-state slot
slot="empty" customizes the empty result rendering (replacing the default "No commands found…" text); the host reads the current query via el.query. Example: a "create xxx" button when nothing matches.
Empty-state slot
Nested pages / breadcrumb fallback
The item field page (array of commands) defines a sub-page: selecting it enters the sub-page (a breadcrumb bar with a back button appears at the top), Esc or Backspace with an empty query goes back, and only at the root does Esc close the panel. Each push/pop fires one oas-page-change.
Nested pages
Declarative child-element channel
Besides the items JSON, commands can be declared with <oas-command-item> child elements (items takes precedence when explicitly set; otherwise the child elements are parsed and converge onto the same rendering path). The default slot text becomes the label, and attributes align with the items fields: value / group / disabled / icon / shortcut / description / view / force-mount / separator; keywords is a comma-separated string participating in search matching. Nested <oas-command-item> elements directly inside an item recursively become its page sub-page. Child additions/removals, attribute and text changes re-render automatically (MutationObserver).
Declarative children (groups / separator / shortcuts / nested sub-page)
Recent history
recent tracks recently used commands: selected items are pinned to the top (deduped, capped at 10); recent-storage-key enables localStorage persistence (restored across instances). This demo swaps the item set after selection to simulate "command set changes" — reopen to see the "Recent" group.
Recent history (localStorage persistence)
Loading and limit
loading shows an async loading placeholder (spinner row + aria-busy); limit caps the rendered item count (default 50).
Loading and limit
Multiple-select commands
multiple mode: Enter/click toggles checks (fires oas-change { values }), and a "Run n" confirm button in the footer executes the batch (fires oas-select { values }).
Multi-select batch run
In-panel views (Raycast style)
The item field view defines a view: selecting it renders the form/panel carried by <slot name="view-{view}"> (deploy params, quick actions…), Esc/breadcrumb goes back. Each enter/exit fires one oas-view-change.
View slot (deploy form)
Controlled query and active item
value is the controlled search query (two-way by echoing oas-input back); selected is the controlled active item (the host echoes oas-active back). close-on-select="false" keeps the panel open after selection for consecutive actions.
Controlled value / selected
Custom footer
slot="footer" replaces the default shortcut hints (↑↓ navigate / ↵ select / esc close).
Footer slot
Mount container (append-to)
append-to: the whole panel (backdrop + palette) moves into a portal host inside the target container (isolated shadow + style injection + slot bridging — empty/footer/view-* slot content travels with the panel), for nested transform / stacking-context scenarios.
append-to mount container
Virtual scrolling
virtual enables windowed rendering for large datasets (reuses oas-virtual-list; falls back to full rendering when groups or recents are present). This demo preloads 20,000 commands and renders only the visible window.
Virtual scrolling (20k items)
API
oas-command
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
append-to | Mount container selector (e.g. #panel): the whole panel (backdrop + palette) moves into a portal host inside the target container (isolated shadow + style injection + slot bridging — empty/footer/view-* slots travel with the panel), for nested transform/stacking-context scenarios; without it the panel is fixed-positioned inside the component's own shadow | string | — |
close-on-select | Close the panel after selecting (default true; false keeps it open for consecutive actions) | string | — |
hotkey | Invocation shortcut combos, e.g. ctrl+k / meta+shift+p (comma-separated for multiple; supports mod/meta/ctrl/alt/shift); false disables the built-in listener (default mod+k) | string | mod+k |
item-height | Virtual row height (default 36) | string | 36 |
items | Command items JSON (root page; sub-pages via item.page, supports icon / shortcut / description / page / view / forceMount / separator fields) | CommandItem[] | string | [] |
limit | Max rendered items (default 50; not applied in virtual mode) | string | 50 |
loading | Async loading placeholder (loading row in the list + aria-busy) | boolean | — |
multiple | Multiple-select mode (toggle checks + batch run via the footer confirm button) | boolean | — |
open | Whether open (controlled; auto-removed after selection / Esc; fires oas-open-change on open and close) | boolean | — |
recent | Track recently used (selected items pinned to top, deduped, capped at 10) | boolean | — |
recent-storage-key | localStorage key for recent persistence (requires recent; restored across instances) | string | — |
selected | Active item value (controlled; arrow keys / hover fire oas-active for the host to echo back) | string | — |
should-filter | false disables built-in filtering and score sorting (filtering delegated to the host, for async/server data sources) | string | true |
value | Search query (controlled; two-way via oas-input echo-back) | string | — |
virtual | Virtual scrolling for large datasets (reuses oas-virtual-list) | boolean | — |
Events
| Event | Description |
|---|---|
oas-active | Active item changed, detail: { value } (basis for controlled selected echo-back) |
oas-change | Multi-select toggle, detail: { values } |
oas-input | Search input, detail: { value } (request channel for should-filter=false external filtering) |
oas-open-change | Panel opened/closed, detail: { open } |
oas-page-change | Sub-page pushed/popped, detail: { title, depth, direction } (direction: push|pop) |
oas-select | A command was executed, detail: { value }; multi-select confirm detail: { values } |
oas-view-change | View entered/exited, detail: { view, title } (exiting sends view: '') |
Slots
| Name | Description |
|---|---|
empty | Custom empty state (read the current query via el.query, e.g. a "create xyz" entry) |
footer | Custom footer bar (defaults to ↑↓ navigate / ↵ select / esc close hints) |
view-${view} | Per-view override of the selected content (host writes <div slot="view-deploy"> …; view is the view field value) |
oas-command-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
description | Secondary description line | — | — |
disabled | Disable this item (not selectable via Enter/click, skipped by arrow keys) | — | — |
force-mount | Ignore filtering and always render (create-type entry) | — | — |
group | Group name (optional); same-group items render a group title | — | — |
icon | Item icon: SVG path d string or a full <svg> markup | — | — |
keywords | Search keywords (comma-separated string, trimmed into string[]), matched in addition to the label | — | — |
separator | Render as a separator row (not navigable/selectable) | — | — |
shortcut | Shortcut label (right-aligned kbd), e.g. meta+p / ctrl+shift+s | — | — |
value | Selection value (data-carrier field of the declarative child channel) | — | — |
view | View slot name: selecting enters <slot name="view-{view}"> (in-panel view) | — | — |
Slots
| Name | Description |
|---|---|
| default | Command item label content (default slot text); direct child <oas-command-item> elements recursively become the page sub-page |
CommandItem fields:
| Field | Description | Type |
|---|---|---|
label | Display text | string |
value | Selected value (oas-select detail.value) | string |
keywords | Search keywords (optional), matched in addition to the label | string[] |
group | Group name (optional); same-group items render a group title | string |
disabled | Disables the item (not selectable via Enter/click, skipped by arrow keys) | boolean |
icon | Item icon (SVG path d string or full <svg> markup; host data, treated as trusted content) | string |
shortcut | Shortcut annotation (rendered as a right-aligned kbd), e.g. "meta+p" / "ctrl+shift+s" | string |
description | Subtitle description text | string |
page | Sub-page commands (nested page): selecting enters the sub-page; Esc / Backspace with an empty query pops back | CommandItem[] |
view | View slot name: selecting enters <slot name="view-{view}"> (in-panel view) | string |
forceMount | Ignores filtering and always shows (create-style entry) | boolean |
separator | Renders as a separator row (not navigable / not selectable) | boolean |
Keyboard: ↑/↓ move the highlight (skipping disabled items), Enter executes and closes (multi-select toggles checks), Esc closes (sub-pages pop first), Backspace with an empty query pops a sub-page, Tab cycles between the search input and the options (focus trap); on open the search input is focused, and on close focus returns to the source element. The global shortcut defaults to ⌘K / Ctrl+K (hotkey is configurable or can be disabled).