A keyboard-driven command palette. Press ⌘K or CtrlK to
open, or just start typing on the page (this demo enables open-on-type).
Press ⌘K (Mac) or CtrlK to open the palette, or use the button below. Type to filter, use arrow keys to navigate, and press Enter to select. Try "Pick DOM elements" for a drill-down demo.
Define commands as JSON in a script tag, then drop in the component.
<!-- 1. Define your commands -->
<script type="application/json" data-palette>
[
{ "name": "save", "description": "Save document" },
{ "name": "pick-thing", "description": "Pick a thing", "keepOpen": true }
]
</script>
<!-- 2. Add the component -->
<command-palette></command-palette>
<script src="command-palette.wc.js"></script>
<!-- 3. Listen for events -->
<script>
const palette = document.querySelector('command-palette');
palette.addEventListener('save', () => {
console.log('Save triggered!');
});
</script>
Use keepOpen to prevent closing on selection, then setCommands() to swap in new
items. Press Backspace on an empty input to go back.
// Command with keepOpen stays open on select
{ "name": "pick-dom", "description": "Pick DOM elements", "keepOpen": true }
// Handle the event: swap in dynamic data
palette.addEventListener('pick-dom', () => {
const elements = [...document.querySelectorAll('[class]')].map(el => ({
name: 'element-picked',
description: `<${el.tagName.toLowerCase()}> .${[...el.classList].join('.')}`
}));
palette.setCommands(elements, {
placeholder: 'Pick an element...'
});
});
// The palette auto-resets to the original
// commands when closed or dismissed
When the root list itself is backed by app state (subscriptions, notes, tabs), use
setBaseCommands(). Unlike setCommands(), this replaces the root list — no back
button, no reset on close. Pair it with onBeforeOpen for just-in-time refresh.
// Refresh the root list whenever the palette is about to open
palette.onBeforeOpen = () => {
const commands = [
{ name: 'quick-add', description: 'Add subscription' },
...state.subscriptions.map(s => ({
name: 'edit-sub',
description: `Edit: ${s.name}`,
_subId: s.id // any extra metadata is preserved in e.detail.command
}))
];
palette.setBaseCommands(commands);
};
// One listener handles every matching command
palette.addEventListener('edit-sub', (e) => {
const sub = state.subscriptions.find(s => s.id === e.detail.command._subId);
openEditor(sub);
});
Methods, events, and keyboard shortcuts.
// Open / close programmatically
palette.open();
palette.close();
// Replace the root command list (for dynamic data)
palette.setBaseCommands(commands);
// Called just before open() shows the panel
palette.onBeforeOpen = () => { /* refresh commands */ };
// Drill-down (adds back button, resets on close)
palette.setCommands(commands, {
placeholder: 'Pick something...', // optional
label: 'Elements' // optional aria-label
});
// Each selected command fires a CustomEvent
// where event.type === command.name
palette.addEventListener('theme-toggle', (e) => {
console.log(e.detail.command);
// { name: "theme-toggle", description: "Toggle theme" }
});
// Declarative open / close via the Invoker Commands API:
// works with any element that has an id.
<command-palette id="palette"></command-palette>
<button commandfor="palette" command="--open">Open</button>
<button commandfor="palette" command="--toggle">Toggle</button>
// Supported custom commands: --open, --close, --toggle
// Opt in to "search-as-you-type": pressing any printable key on the
// page (when no input/textarea/contenteditable is focused) opens the
// palette pre-filled with that character. "/" opens without populating.
<command-palette open-on-type></command-palette>