component
A ⌘K-style launcher: a modal Dialog whose header is a search field and whose body is a fuzzy-filtered ListBox of commands. It composes three existing primitives — the Dialog shell (with its new header slot), the Input search field, and the ListBox option engine — behind one headless connectCommandPalette core.
Like the Combobox, the field keeps DOM focus and drives the list with aria-activedescendant (the ListBox’s focusStrategy="active-descendant", revealStrategy="deferred" modes). Arrow keys move the cursor without leaving the input; Enter runs the focused command and closes the palette. Unlike a combobox, nothing is “selected” — every row is an action (selectionMode="none"), so activating one fires onCommand and dismisses.
Open the palette and type to filter. Matching is fuzzy — nf finds “New File” — and results rank best-match-first. Each command can carry a leading icon, extra keywords (matched but not shown), and a shortcut hint rendered on the trailing edge.
---
import CommandPalette from "../command-palette.astro";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "sm", direction: "column", align: "start" })}>
<Button id="astro-cmdp-default-trigger">Open command palette</Button>
<CommandPalette
id="astro-cmdp-default"
items={[
{
id: "new-file",
label: "New File",
shortcut: ["⌘", "N"],
keywords: ["create"],
},
{ id: "open-file", label: "Open File…", shortcut: ["⌘", "O"] },
{
id: "settings",
label: "Open Settings",
shortcut: ["⌘", ","],
keywords: ["preferences"],
},
{ id: "profile", label: "View Profile" },
{ id: "theme", label: "Toggle Theme", keywords: ["dark", "light"] },
]}
/>
</div>
<script>
document
.getElementById("astro-cmdp-default-trigger")
?.addEventListener("click", () => {
window.__PindobaDialogManager?.open("astro-cmdp-default-dialog");
});
</script>Group commands into sections with { type: "section", title, items }. Sections render as labelled groups while the query is empty; once you start typing, results flatten and rank globally so the strongest match is always the top row — the norm for a command palette.
---
import CommandPalette from "../command-palette.astro";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "sm", direction: "column", align: "start" })}>
<Button id="astro-cmdp-grouped-trigger">Open palette</Button>
<CommandPalette
id="astro-cmdp-grouped"
items={[
{
id: "navigation",
type: "section",
title: "Navigation",
items: [
{ id: "go-home", label: "Go to Home", shortcut: "g h" },
{ id: "go-inbox", label: "Go to Inbox", shortcut: "g i" },
{ id: "go-calendar", label: "Go to Calendar", shortcut: "g c" },
],
},
{
id: "actions",
type: "section",
title: "Actions",
items: [
{ id: "archive", label: "Archive", shortcut: "e" },
{ id: "share", label: "Share…" },
{ id: "delete", label: "Delete", shortcut: ["⌘", "⌫"] },
],
},
]}
/>
</div>
<script>
document
.getElementById("astro-cmdp-grouped-trigger")
?.addEventListener("click", () => {
window.__PindobaDialogManager?.open("astro-cmdp-grouped-dialog");
});
</script>Set isLoading while a command source is being fetched; the body shows a Loading spinner in place of results. The Svelte demo simulates a fetch on open, then swaps in the commands.
---
import CommandPalette from "../command-palette.astro";
import Button from "@pindoba/astro-button";
import { stack } from "@pindoba/styled-system/patterns";
// Static demo of the loading state — `isLoading` renders the spinner in place
// of results (an async source would flip this off once its commands arrive).
---
<div class={stack({ gap: "sm", direction: "column", align: "start" })}>
<Button id="astro-cmdp-async-trigger">Open (loading state)</Button>
<CommandPalette
id="astro-cmdp-async"
isLoading
labels={{ placeholder: "Search deployments…" }}
items={[
{ id: "deploy", label: "Deploy to Production", shortcut: ["⌘", "D"] },
{ id: "rollback", label: "Roll Back Last Deploy" },
{ id: "logs", label: "Open Logs" },
]}
/>
</div>
<script>
document
.getElementById("astro-cmdp-async-trigger")
?.addEventListener("click", () => {
window.__PindobaDialogManager?.open("astro-cmdp-async-dialog");
});
</script>Set hotkey to register a global open shortcut — true binds ⌘K / Ctrl+K, or pass a key string (still requires ⌘/Ctrl). Leave it off (the default) to control open yourself (a trigger button, a menu item, your own key handler).
---
import CommandPalette from "../command-palette.astro";
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "sm", direction: "column", align: "start" })}>
<span>
Press <Badge size="sm">⌘ K</Badge> / <Badge size="sm">Ctrl K</Badge> to open the
palette.
</span>
<CommandPalette
id="astro-cmdp-hotkey"
hotkey
items={[
{ id: "search", label: "Search Everywhere", shortcut: ["⌘", "P"] },
{ id: "command", label: "Run Command", shortcut: ["⌘", "⇧", "P"] },
{ id: "goto", label: "Go to Line", shortcut: ["⌘", "G"] },
{ id: "format", label: "Format Document", shortcut: ["⌥", "⇧", "F"] },
]}
/>
</div>The built-in fuzzy matcher (filter={true}, the default) ranks by match quality. To plug in your own ranking — or to drive results from a server — pass a filter function (command, query) => number | boolean | null: return a score (higher ranks first), true/false to keep/drop without ranking, or null to drop. Pass filter={false} to disable client filtering entirely and feed pre-filtered items yourself.
The bound `<input>` element.
Custom empty state (replaces "No commands found").
Feedback (color) scheme applied to the palette.
trueFilter/ranking strategy. `true` = built-in fuzzy, `false` = no filtering (consumer pre-filters), or a `(command, query) => score | boolean | null` predicate (higher score ranks first).
Optional footer row under the list (e.g. nav hints).
falseRegister a global open hotkey. `true` = ⌘K / Ctrl+K; a string sets the key (still requires ⌘/Ctrl). `false` (default) leaves open control to the consumer.
Stable id overrides for the palette's dialog, input, and listbox elements.
falseWhether command sources are still loading (shows the loading state).
Commands to show, flat or grouped into sections.
Accessible names + the search placeholder copy.
Custom field leading content, rendered inside the leading `<Affix>` (replaces the default search glyph). The affix is decorative by default — opt out via `passThrough.searchIcon.props.decorative = false`.
Custom loading state (replaces the default spinner).
Fired when a command is activated (Enter / click). The palette then closes.
Fired when the open state changes.
Fired when the query text changes.
falseWhether the palette is open (baked onto the field's `aria-expanded`).
Per-slot style/attribute overrides for the Dialog, field, and ListBox.
Current search query (the host owns the text; the connect filters by it).
trueShow each command's `shortcut` hint on its trailing edge.
"md"Size variant for the field and result rows.
Plus all standard <dialog> HTML
attributes.