<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
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
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
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
| Event | Detail | Description |
|---|---|---|
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
| Method | Signature | Description |
|---|---|---|
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
| Slot | Description |
|---|---|
empty |
Shown instead of "No results". |
footer |
Replaces the keyboard hints along the foot. |
CSS shadow parts
| Part | Description |
|---|---|
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
| Property | Description |
|---|---|
--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. |