v2.0.0

<bmx-command-palette>

Every action in an application, one keystroke away. Ctrl+K (Command+K on a Mac) opens a search box over the page; typing finds commands by their label, keywords, group or description, letters in order ("nwi" finds "New invoice"); Enter runs the one chosen.

15 properties · 4 events · 6 methods · 17 parts

Example

Open the command palette or press Ctrl + K (⌘ + K on a Mac)

Try “nwi”, “assign”, or a customer name such as “acme”. Ctrl + Shift + I runs “New invoice” from anywhere.

Inline, on the page

Show markup
<div class="row" style="display: block">
  <p style="margin: 0 0 12px">
    <bmx-button id="ex-cp-open" variant="outline">Open the command palette</bmx-button>
    <span class="note">or press <kbd>Ctrl</kbd> + <kbd>K</kbd> (<kbd>⌘</kbd> + <kbd>K</kbd> on a Mac)</span>
  </p>
  <bmx-command-palette id="ex-cp" global-shortcuts></bmx-command-palette>
</div>
<p class="note" id="ex-cp-out" role="status">Try “nwi”, “assign”, or a customer name such as “acme”. <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd> runs “New invoice” from anywhere.</p>

<h3>Inline, on the page</h3>

<div class="row" style="display: block">
  <bmx-command-palette id="ex-cp-inline" mode="inline" hotkey="" label="Quick actions" placeholder="What would you like to do?" style="--bmx-command-palette-list-height: 15rem"></bmx-command-palette>
</div>

<script type="module">
  await customElements.whenDefined('bmx-command-palette');
  const out = document.getElementById('ex-cp-out');
  const icon = d => `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="${d}"/></svg>`;
  const people = ['Ann Lee', 'Raj Patel', 'Mei Chen', 'Tom Hughes', 'Sara Okafor'];
  const commands = [
    { id: 'new-invoice', label: 'New invoice', group: 'Finance', keywords: ['create', 'bill'], shortcut: 'mod+shift+i', icon: icon('M14 3H6v18h12V7zM14 3v4h4M9 13h6M9 17h6') },
    { id: 'payment', label: 'Record a payment', group: 'Finance', description: 'Against an open invoice', icon: icon('M3 7h18v10H3zM3 11h18') },
    { id: 'statement', label: 'Customer statement', group: 'Finance', badge: 'New', icon: icon('M4 4h16v16H4zM8 9h8M8 13h8M8 17h5') },
    {
      id: 'assign',
      label: 'Assign ticket to…',
      group: 'Tickets',
      placeholder: 'Choose a person',
      icon: icon('M16 19v-1a4 4 0 0 0-8 0v1M12 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6'),
      children: () => new Promise(resolve => setTimeout(() => resolve(people.map(p => ({ id: p, label: p }))), 300)),
    },
    { id: 'close-ticket', label: 'Close ticket', group: 'Tickets', shortcut: 'mod+shift+x', icon: icon('M5 12l4 4 10-10') },
    {
      id: 'theme',
      label: 'Change theme…',
      group: 'Settings',
      icon: icon('M12 3a9 9 0 1 0 9 9 7 7 0 0 1-9-9z'),
      children: [
        { id: 'light', label: 'Light' },
        { id: 'dark', label: 'Dark' },
        { id: 'system', label: 'Same as the system' },
      ],
    },
    { id: 'archive', label: 'Archive old records', group: 'Settings', disabled: true, description: 'Administrators only' },
    { id: 'docs', label: 'Read the documentation', group: 'Help', href: '#', keywords: ['manual', 'guide'] },
  ];
  const customers = ['Acme Engineering', 'Acme Foods', 'Blue Harbour Ltd', 'Northwind Traders', 'Quayside Logistics'];
  const palette = document.getElementById('ex-cp');
  palette.commands = commands;
  palette.providers = [
    {
      id: 'customers',
      group: 'Customers',
      minQuery: 2,
      search: q => new Promise(resolve => setTimeout(() => resolve(customers.filter(c => c.toLowerCase().includes(q.toLowerCase())).map(c => ({ id: `cust:${c}`, label: c, description: 'Open the customer record' }))), 250)),
    },
  ];
  palette.addEventListener('bmxCommand', e => {
    e.preventDefault();
    const path = e.detail.path.map(c => c.label.replace('…', '')).join(' › ');
    out.textContent = `Ran: ${path ? `${path} › ` : ''}${e.detail.command.label}`;
  });
  document.getElementById('ex-cp-open').addEventListener('click', () => palette.openPalette());

  const inline = document.getElementById('ex-cp-inline');
  inline.commands = commands.slice(0, 6);
  inline.addEventListener('bmxCommand', e => {
    e.preventDefault();
    out.textContent = `Ran from the inline palette: ${e.detail.command.label}`;
  });
</script>
<bmx-command-palette id="palette"></bmx-command-palette>
<script>
  palette.commands = [
    { id: 'new-invoice', label: 'New invoice', group: 'Finance', shortcut: 'mod+shift+i', action: () => newInvoice() },
    { id: 'assign', label: 'Assign to…', group: 'Tickets', children: () => api.people() },
    { id: 'help', label: 'Help centre', href: '/help' },
  ];
</script>

A command runs its action, follows its href, or - with children - opens a page of further commands ("Assign to…" then a person); Backspace in the empty box, or Escape, goes back. Recent commands are listed first; the list is the page's to keep (recent, bmxRecentChange) - nothing is stored. providers search elsewhere as the reader types: the page's own customers, documents or tickets, debounced, the call before aborted. global-shortcuts runs a command from its shortcut anywhere on the page, so the palette is also the one list of an application's keyboard shortcuts.

In the default dialog mode it is a modal over the page (the browser's own <dialog>: top layer, everything else inert, focus back where it was). mode="inline" draws it in place - a launcher on a home page, a sidebar.

The search box is a combobox owning a listbox of grouped options; the current option is aria-activedescendant; each option's shortcut is in aria-keyshortcuts; the number of results is announced.

Properties

PropertyAttributeTypeDefaultDescription
closeOnBackdrop close-on-backdrop boolean true Whether a press on the scrim closes it (dialog mode).
commands commands BmxCommand[] | string [] The commands, as objects or JSON.
filter property only (command: BmxCommand, query: string) => number | null | undefined — A score for each command against the query, replacing the built-in matching; null leaves it out.
globalShortcuts global-shortcuts boolean false Run a command from its shortcut wherever focus is on the page, while the palette is closed.
hideHints hide-hints boolean false Leave out the keyboard hints along the foot.
hotkey hotkey string 'mod+k' The keys that open it (dialog mode) or focus it (inline): mod is Command on Apple systems and Control elsewhere. Several separated by commas; empty for none.
label label string — The palette's accessible name. Default: "Command palette".
maxRecent max-recent number 5 The longest the recent list grows. 0 keeps none.
maxResults max-results number 100 The most results listed at once, across groups. Results from providers are listed beyond it.
mode mode 'dialog' | 'inline' 'dialog' dialog: a modal over the page, opened by the hotkey. inline: drawn in place, always showing.
open open boolean false Whether it is showing (dialog mode). Mutable: the reader opens and closes it.
placeholder placeholder string — The search box's placeholder.
providers property only BmxCommandProvider[] [] Sources searched as the reader types (from script).
recent recent string[] | string [] Recent command ids, newest first. Mutable: running a command puts it first.
strings strings Partial<BmxCommandPaletteStrings> | string — Words to show instead of the English ones, as an object or JSON.

Events

EventDetailDescription
bmxCommand BmxCommandRunDetail Fired before a command runs. Cancel it to run the command yourself; the palette still closes.
bmxOpenChange boolean Fired when the palette opens or closes.
bmxQueryChange BmxCommandQueryDetail Fired as the query or the page changes.
bmxRecentChange BmxCommandRecentDetail Fired when running a command changes the recent list.

Methods

MethodSignatureDescription
back back() => Promise<boolean> Back one page. Resolves whether there was one to go back from.
closePalette closePalette() => Promise<void> Closes the palette.
openPage openPage(id: string) => Promise<boolean> Opens the page of a top-level command that has children, by id. Resolves whether it did.
openPalette openPalette(query?: string) => Promise<void> Opens the palette, optionally with a query typed in. In inline mode, focuses it.
setQuery setQuery(query: string) => Promise<void> Types a query into the box.
togglePalette togglePalette() => Promise<void> Opens it when closed, closes it when open.

Slots

SlotDescription
empty Shown instead of "No results".
footer Replaces the keyboard hints along the foot.

CSS shadow parts

PartDescription
badge A result's tag.
crumb A chosen command on the way to the current page.
description A result's second line.
dialog The native dialog, which is also the scrim (dialog mode).
empty The "No results" message.
footer The hints along the foot.
heading A group's heading.
icon A result's icon.
input The search box.
label A result's label.
list The list of results.
match The letters of a label that matched.
option One result; also option-active and option-disabled.
panel The box everything sits in.
search The row with the search box.
shortcut A result's keys.
spinner

CSS custom properties

PropertyDescription
--bmx-command-palette-active The background of the chosen result.
--bmx-command-palette-list-height The tallest the list grows before it scrolls.
--bmx-command-palette-match The colour of the letters that matched.
--bmx-command-palette-radius Corner radius of the panel.
--bmx-command-palette-scrim The colour laid over the page behind it.
--bmx-command-palette-top Space above the panel in dialog mode.
--bmx-command-palette-width The panel's width.