kit

Command palette

Search everything and jump anywhere from ⌘K, with grouped results, keyboard-first, and server results merged in as they arrive.

Tier 2 · behaviour APG combobox in a dialog beta

Example

Chosen: nothing yet
Overview Settings Members
Show the markup
<div class="kit-cluster" data-gap="3">
  <button class="kit-button" type="button" commandfor="demo-palette" command="--open"><svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true"><circle cx="9" cy="9" r="5.5"/><path d="m13.2 13.2 3.3 3.3"/></svg>Search <kbd class="kit-kbd">⌘K</kbd></button>
  <span class="demo-note" id="palette-out">Chosen: nothing yet</span>
</div>
<kit-command id="demo-palette" label="Search the demo" data-demo-note="palette-out">
  <a href="#command-palette" data-group="Pages" data-keywords="home start">Overview</a>
  <a href="#command-palette" data-group="Pages" data-keywords="preferences account">Settings</a>
  <a href="#command-palette" data-group="Pages">Members</a>
  <button type="button" value="new-item" data-group="Actions" data-hint="N">New item</button>
  <button type="button" value="duplicate" data-group="Actions" data-hint="D">Duplicate item</button>
  <button type="button" value="theme" data-group="Actions" data-keywords="dark light appearance">Switch theme</button>
  <button type="button" value="delete" data-group="Danger zone">Delete item</button>
</kit-command>

Usage

<kit-command id="palette" label="Search commands">
  <a href="/settings" data-group="Pages" data-keywords="preferences account">Settings</a>
  <button value="new" data-group="Actions" data-hint="N">New project</button>
</kit-command>
<button class="kit-button" commandfor="palette" command="--open">Search <kbd class="kit-kbd">⌘K</kbd></button>

The links and buttons inside are its items; they stay hidden until the palette lists them. Choosing a link follows it, and choosing a button clicks it, so handlers already on the button run. A cancelable select event fires first, with { value, item }.

Attribute Values Default
shortcut a letter pressed with ⌘ (Mac) or Ctrl (elsewhere); empty for none k
label the palette's accessible name "Command palette"
placeholder the search box's placeholder "Search…"
data-group, data-keywords, data-hint on each item: its heading, extra words that find it, a shortcut shown beside it none

For results from a server, set source to an async function. It's called with the query and an AbortSignal that aborts when the person types again, so only the latest results show:

palette.source = async (q, signal) => {
  const r = await fetch(`/search?q=${encodeURIComponent(q)}`, { signal });
  return (await r.json()).map((x) => ({ label: x.title, group: 'Sites', href: x.url }));
};

Every word typed must appear in an item's label, keywords or group; items whose label starts with the words rank first, and groups are ordered by their best match.

Accessibility

  • It's a modal dialog: the page behind is inert, Escape closes it and focus returns to where it was.
  • The search box is a combobox controlling a listbox of grouped options; arrows move the highlighted option (aria-activedescendant) while focus stays in the box, and Enter chooses.
  • After a pause in typing, the number of results is announced.
  • The shortcut is a convenience: always offer a visible button that opens the palette too.