Command Palette
Accessible, unstyled command palette built on the command router, Dialog, and composite navigation.
|
|
| Package |
@vizejs/ui/command-palette |
| Maturity |
stable |
| Own the source |
vize lib pull command-palette |
| Requires |
collection, command, composite-navigation, context, controllable-state, dialog, id, live-region, shortcut |
| Aliases |
command palette, command menu, cmdk, spotlight, quick open |
| Covers |
WAI-ARIA Combobox with listbox popup, cmdk, Ark UI Combobox, VS Code Command Palette |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { CommandPalette, CommandPaletteRoot, CommandPaletteDialog, CommandPaletteEmpty, CommandPaletteGroup, CommandPaletteInput, CommandPaletteItem, CommandPaletteList, CommandPaletteLoading } from "@vizejs/ui/command-palette";
Or copy the source into your project with vize lib pull command-palette (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Inline command palette that filters grouped actions as the user types and reports the last one run. -->
<script setup lang="ts">
import { ref } from "vue";
import {
CommandPalette,
CommandPaletteEmpty,
CommandPaletteGroup,
CommandPaletteInput,
CommandPaletteItem,
CommandPaletteList,
} from "@vizejs/ui/command-palette";
const lastAction = ref<string | null>(null);
function run(action: string | undefined): void {
if (action !== undefined) lastAction.value = action;
}
</script>
<template>
<div>
<CommandPalette>
<CommandPaletteInput aria-label="Search commands" placeholder="Type a command" />
<CommandPaletteList aria-label="Commands">
<CommandPaletteGroup heading="Files">
<CommandPaletteItem
value="new-file"
text-value="New file"
shortcut="Control+N"
@select="run"
/>
<CommandPaletteItem
value="open-file"
text-value="Open file"
shortcut="Control+O"
@select="run"
/>
</CommandPaletteGroup>
<CommandPaletteGroup heading="Preferences">
<CommandPaletteItem
value="toggle-theme"
text-value="Toggle dark theme"
:keywords="['appearance', 'color']"
@select="run"
/>
<CommandPaletteItem value="open-settings" text-value="Open settings" @select="run" />
</CommandPaletteGroup>
<CommandPaletteEmpty>No commands found</CommandPaletteEmpty>
</CommandPaletteList>
</CommandPalette>
<output>Last command: {{ lastAction ?? "none" }}</output>
</div>
</template>
API
CommandPalette<Id extends string = string>
Source: src/families/overlays/command-palette/command-palette-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled visibility of the results list (aria-expanded on the input). |
defaultOpen |
boolean |
true |
Initial uncontrolled results visibility. |
search |
string |
undefined |
Controlled search text. undefined selects uncontrolled behavior. |
defaultSearch |
string |
"" |
Initial uncontrolled search text. |
router |
CommandRouter<Id> |
undefined |
Command router whose commands items can run and whose metadata the default slot receives as commands. |
filter |
CommandPaletteFilter |
defaultCommandPaletteFilter |
Item scorer. 0 hides an item. The default ranks exact, prefix, word-prefix, substring, and subsequence matches. |
shouldFilter |
boolean |
true |
Filter items locally. Set false when results are already filtered, for example by a server search. |
loading |
boolean |
false |
Results are loading; the list reports aria-busy. |
loop |
boolean |
true |
Wrap arrow-key navigation at the first and last item. |
recent |
readonly Id[] |
undefined |
Controlled most-recent-first command ids. |
defaultRecent |
readonly Id[] |
[] |
Initial uncontrolled recent command ids. |
recentLimit |
number |
5 |
Maximum number of recent command ids kept. |
resultsLabel |
CommandPaletteResultsLabel |
defaultCommandPaletteResultsLabel |
Live-region announcement for the visible result count. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when results visibility requests a new controlled value. |
update:search |
[value: string] |
Fired when the search text requests a new controlled value. |
update:recent |
[value: readonly Id[]] |
Fired when running a router command updates the recent list. |
select |
[commandId: Id | null, nativeEvent: Event | null] |
Fired after an item was selected; commandId is null for plain items. |
Slots
| Slot |
Slot props |
Description |
default |
CommandPaletteSlotState<Id> |
Palette parts. Receives search, result, and router command state. |
Exposed
| Member |
Type |
Description |
activeItemId |
— |
|
focusInput |
— |
|
id |
— |
|
listId |
— |
|
open |
— |
|
resultCount |
— |
|
search |
— |
|
selectActive |
— |
|
setOpen |
— |
|
setSearch |
— |
|
CommandPaletteRoot<Id extends string = string>
Source: src/families/overlays/command-palette/command-palette-root.vue
Props
| Prop |
Type |
Default |
Description |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
open |
boolean |
undefined |
Controlled visibility of the results list (aria-expanded on the input). |
defaultOpen |
boolean |
true |
Initial uncontrolled results visibility. |
search |
string |
undefined |
Controlled search text. undefined selects uncontrolled behavior. |
defaultSearch |
string |
"" |
Initial uncontrolled search text. |
router |
CommandRouter<Id> |
undefined |
Command router whose commands items can run and whose metadata the default slot receives as commands. |
filter |
CommandPaletteFilter |
defaultCommandPaletteFilter |
Item scorer. 0 hides an item. The default ranks exact, prefix, word-prefix, substring, and subsequence matches. |
shouldFilter |
boolean |
true |
Filter items locally. Set false when results are already filtered, for example by a server search. |
loading |
boolean |
false |
Results are loading; the list reports aria-busy. |
loop |
boolean |
true |
Wrap arrow-key navigation at the first and last item. |
recent |
readonly Id[] |
undefined |
Controlled most-recent-first command ids. |
defaultRecent |
readonly Id[] |
[] |
Initial uncontrolled recent command ids. |
recentLimit |
number |
5 |
Maximum number of recent command ids kept. |
resultsLabel |
CommandPaletteResultsLabel |
defaultCommandPaletteResultsLabel |
Live-region announcement for the visible result count. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when results visibility requests a new controlled value. |
update:search |
[value: string] |
Fired when the search text requests a new controlled value. |
update:recent |
[value: readonly Id[]] |
Fired when running a router command updates the recent list. |
select |
[commandId: Id | null, nativeEvent: Event | null] |
Fired after an item was selected; commandId is null for plain items. |
Slots
| Slot |
Slot props |
Description |
default |
CommandPaletteSlotState<Id> |
Palette parts. Receives search, result, and router command state. |
Exposed
| Member |
Type |
Description |
id |
string |
Root-owned base id. |
listId |
string |
Id of the rendered listbox. |
open |
boolean |
Whether results are showing. |
search |
string |
Current search text. |
resultCount |
number |
Number of visible items. |
activeItemId |
string | null |
Id of the active item, or null. |
setSearch |
(value: string) => boolean |
Replace the search text and report whether it changed. |
setOpen |
(value: boolean) => boolean |
Request a specific open value and report whether it changed. |
selectActive |
(event?: Event | null) => boolean |
Select the active item as if Enter was pressed. Returns whether an item was selected. |
focusInput |
(options?: FocusOptions) => void |
Focus the search input. |
CommandPaletteDialog
Source: src/families/overlays/command-palette/command-palette-dialog.vue
Props
| Prop |
Type |
Default |
Description |
open |
boolean |
undefined |
Controlled dialog visibility. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial uncontrolled dialog visibility. |
shortcut |
string | null |
"Mod+K" |
Global shortcut that toggles the dialog, resolved per platform (Mod is Command on Apple platforms and Control elsewhere). null disables it. |
closeOnSelect |
boolean |
true |
Close the dialog after an item is selected. |
ariaLabel |
string |
"Command palette" |
Accessible name of the dialog. |
to |
string | HTMLElement |
"body" |
CSS selector or element the dialog layer is moved into. |
portalDisabled |
boolean |
false |
Render the dialog in place instead of teleporting it. |
Events
| Event |
Payload |
Description |
update:open |
[value: boolean] |
Fired when the dialog requests a controlled open value. |
Slots
| Slot |
Slot props |
Description |
default |
{ readonly open: boolean; readonly close: ( |
A CommandPaletteRoot and its parts. Receives the dialog state and a close function. |
Exposed
| Member |
Type |
Description |
open |
boolean |
Whether the dialog is open. |
setOpen |
(value: boolean) => boolean |
Request a specific open value and report whether it changed. |
toggle |
() => boolean |
Toggle the dialog. |
CommandPaletteEmpty
Source: src/families/overlays/command-palette/command-palette-empty.vue
Slots
| Slot |
Slot props |
Description |
default |
{ readonly search: string } |
Fallback shown when no item matches and nothing is loading. Receives the search. |
CommandPaletteGroup
Source: src/families/overlays/command-palette/command-palette-group.vue
Props
| Prop |
Type |
Default |
Description |
heading (required) |
string |
required |
Visible group heading, also used as the group's accessible name. |
forceMount |
boolean |
false |
Keep the group visible even when no item matches the search. |
Slots
| Slot |
Slot props |
Description |
default |
CommandPaletteGroupSlotState |
Group items. Receives the heading and visible item count. |
heading |
CommandPaletteGroupSlotState |
Custom heading contents. Receives the heading and visible item count. |
Source: src/families/overlays/command-palette/command-palette-input.vue
Props
| Prop |
Type |
Default |
Description |
placeholder |
string |
undefined |
Placeholder text shown while the search is empty. |
ariaLabel |
string |
"Search commands" |
Accessible name of the search combobox. |
autofocus |
boolean |
false |
Move focus into the input after mount. |
disabled |
boolean |
false |
Disable the search input. |
Events
| Event |
Payload |
Description |
keydown |
[nativeEvent: KeyboardEvent] |
Fired before palette key handling. Call preventDefault() to skip it. |
Exposed
| Member |
Type |
Description |
element |
HTMLInputElement | null |
Rendered input element. |
focus |
(options?: FocusOptions) => void |
Move focus to the input. |
CommandPaletteItem<Value = undefined>
Source: src/families/overlays/command-palette/command-palette-item.vue
Props
| Prop |
Type |
Default |
Description |
value |
Value |
undefined |
Consumer data handed back through the select event. |
textValue |
string |
undefined |
Searchable label. undefined uses the router command title, then the rendered text after mount. |
keywords |
readonly string[] |
[] |
Extra search terms. Router command keywords are appended. |
command |
string |
undefined |
Router command id run with source "palette" when the item is selected. |
shortcut |
string |
undefined |
Keyboard shortcut advertised through aria-keyshortcuts, for example "Control+K". |
disabled |
boolean |
false |
Refuse selection and skip the item during keyboard navigation. |
forceMount |
boolean |
false |
Keep the item visible whatever the search. |
Events
| Event |
Payload |
Description |
select |
[value: Value | undefined, nativeEvent: Event | null] |
Fired when the item is selected by click, Enter, or select(). |
Slots
| Slot |
Slot props |
Description |
default |
CommandPaletteItemSlotState |
Item contents. Receives active, disabled, and label state. |
Exposed
| Member |
Type |
Description |
active |
boolean |
Whether this item is the active descendant. |
disabled |
boolean |
Whether selection is refused. |
state |
CommandPaletteItemState |
Stable state token for styling and tests. |
textValue |
string |
Searchable label of the item. |
shortcut |
string | null |
Keyboard shortcut advertised through aria-keyshortcuts, or null. |
id |
string |
Stable item id used by aria-activedescendant. |
visible |
boolean |
Whether the current search keeps the item visible. |
element |
HTMLDivElement | null |
Rendered option element. |
select |
(event?: Event | null) => boolean |
Select this item. Returns whether selection ran. |
CommandPaletteList
Source: src/families/overlays/command-palette/command-palette-list.vue
Props
| Prop |
Type |
Default |
Description |
ariaLabel |
string |
"Commands" |
Accessible name of the listbox. |
Slots
| Slot |
Slot props |
Description |
default |
Omit<CommandPaletteSlotState, "recentCommands"> |
Items, groups, empty, and loading parts. |
Exposed
| Member |
Type |
Description |
element |
HTMLDivElement | null |
Rendered listbox element. |
CommandPaletteLoading
Source: src/families/overlays/command-palette/command-palette-loading.vue
Props
| Prop |
Type |
Default |
Description |
label |
string |
"Loading results" |
Accessible name of the loading indicator. |
Slots
| Slot |
Slot props |
Description |
default |
— |
Loading indicator contents. |
Behavior
Normative behavior for @vizejs/ui/command-palette, an APG combobox with a listbox popup
built on the command router, collection + composite-navigation (active descendant),
live-region, shortcut, and the Dialog parts. Every row names its proving test.
| State x input |
Observable outcome |
Proven by |
| any render |
The input is role="combobox" with aria-autocomplete="list", aria-expanded, aria-controls to the listbox, and aria-activedescendant on the first enabled option. |
renders combobox and listbox semantics with an active descendant |
| options and groups |
Options are role="option" with aria-selected for the active one, aria-disabled, and aria-keyshortcuts; groups are role="group" labelled by their heading. |
renders combobox and listbox semantics with an active descendant |
| typing in the input |
Items are scored against label and keywords; non-matching items and empty groups get hidden; the first visible option becomes active; update:search fires. |
typing filters items and groups, resets the active item, and announces the count |
| search changes after mount |
A polite live region announces the visible result count (resultsLabel). |
typing filters items and groups, resets the active item, and announces the count |
| no visible results, not loading |
CommandPaletteEmpty is shown and the listbox reports data-empty. |
typing filters items and groups, resets the active item, and announces the count |
| ArrowDown / ArrowUp / Home / End |
The active option moves, skipping disabled and hidden options, wrapping when loop. |
arrow keys, Home, and End move the active option and skip disabled ones |
| Enter |
The active option is selected: item select(value, event), root select(commandId, event); composing (IME) keystrokes are ignored. |
Enter selects the active item, IME composition is ignored, and Escape clears the search |
| Escape |
A non-empty search is cleared and the key is consumed; an empty search lets Escape reach an enclosing dialog. |
Enter selects the active item, IME composition is ignored, and Escape clears the search |
| pointer over / click on an option |
pointermove activates it; mousedown is prevented to keep focus in the input; click selects unless disabled. |
pointer movement activates options and clicks select without stealing focus |
| router commands |
The root slot receives filtered commands; items with command take its title/keywords, reflect when as disabled, run with source "palette", and update recent. |
router commands render through the slot, run with the palette source, and track recents |
shouldFilter=false, custom filter, loading, controlled search |
Local filtering is skipped, custom scores decide visibility, the listbox is aria-busy with the loading part shown and empty hidden, and controlled search waits for the parent. |
shouldFilter=false, custom filters, loading, and controlled search are honored |
closed palette (open=false) |
The listbox is hidden, aria-expanded="false", no active descendant; ArrowDown requests update:open(true). |
closed palettes hide the list and arrows reopen it |
CommandPaletteDialog, Mod+K |
The global shortcut toggles the dialog (Command on Apple, Control elsewhere); a selection closes it when closeOnSelect; shortcut=null disables. |
the dialog toggles with Mod+K and closes after a selection |
| default filter |
Exact > prefix > word prefix > substring > subsequence; case and diacritics are ignored; keywords count slightly less. |
default filter ranks exact, prefix, word, substring, and subsequence matches |
| parts outside the root |
Mounting throws VIZE_UI_CONTEXT_MISSING. |
palette parts require a root provider |
| SSR |
Isolated requests render byte-identical markup; the closed dialog renders no content; the shortcut listener attaches only after mount. |
renders byte-identical palette markup across isolated SSR requests |
| hydration |
Hydration reuses server nodes with zero warnings and then activates the first visible option. |
hydrates without mismatches and activates the first visible option |
| public types |
Router id unions flow into recent ids and select; item value types the item select payload. |
src/families/overlays/command-palette/command-palette.types.test-d.ts |
Components
| Component |
Contract |
command-palette-root.vue |
Generic over router command ids; owns search, open, recent, scoring, active option, and the result announcer. |
command-palette-input.vue |
Combobox input: typing, arrows, Home/End, Enter, Escape, IME-safe. |
command-palette-list.vue |
Listbox popup with aria-busy and data-empty. |
command-palette-group.vue |
Labelled option group hidden when it has no visible options (unless forceMount). |
command-palette-item.vue |
Generic over value; option with keywords, router command, shortcut, and select. |
command-palette-empty.vue |
Fallback shown when nothing matches and nothing is loading. |
command-palette-loading.vue |
progressbar shown while loading. |
command-palette-dialog.vue |
Dialog composition with the Mod+K toggle; closes after selection. |
Parts And Data
| Target |
Public contract |
| Root |
data-vize-ui="command-palette-root", part="root", data-state, data-loading |
| Input |
data-vize-ui="command-palette-input", part="input", data-state |
| List |
data-vize-ui="command-palette-list", part="list", data-state, data-empty |
| Group |
data-vize-ui="command-palette-group", heading part="group-heading", data-empty |
| Item |
data-vize-ui="command-palette-item", part="item", data-state, data-command |
| Dialog |
data-vize-ui="command-palette-dialog", part="dialog", data-state, data-shortcut |
CommandPalette ships no stylesheet; the announcer is visually hidden with inline styles.