Installation
import { WrCommandPalette, type WrCommandItem } from 'ngwr/command-palette';
@Component({ imports: [WrCommandPalette] })
export class AppShell {
protected readonly commands: WrCommandItem[] = [
{ id: 'theme.light', label: 'Switch to light theme', group: 'Theme', shortcut: 'T L' },
{ id: 'theme.dark', label: 'Switch to dark theme', group: 'Theme', shortcut: 'T D' },
{ id: 'docs.search', label: 'Search docs', group: 'Docs', shortcut: '/' },
];
protected onPicked(item: WrCommandItem) {
console.log('picked', item.id);
}
}Basic usage
Drop a single palette at your app shell. The default hotkey mod+k (⌘K on macOS, Ctrl+K elsewhere) opens it. The (picked) output fires after the item's own action() callback if it has one.
<!-- Drop at the root once; opens via global hotkey (default: mod+k). -->
<wr-command-palette
[items]="commands"
trigger="mod+k"
(picked)="onPicked($event)"
/>Live demo
Click the button or press ⌘K / Ctrl+K. Type to filter. ↑/↓ to move, Enter to pick, Esc to close.
Responsive (full-screen)
With responsive — or app-wide via provideWrResponsiveOverlays() — the palette fills the screen from the top edge on small viewports instead of a centred modal (it auto-focuses its input, so it stays clear of the on-screen keyboard). Open this on a phone (or narrow the window below 640px).
<wr-command-palette responsive [items]="items" [(open)]="open" />Programmatic control
Set [(open)] and pass [trigger] of null to disable the auto-bound hotkey when you want to open it from your own UI.
<button (click)="open.set(true)">Open palette</button>
<wr-command-palette
[items]="commands"
[(open)]="open"
[trigger]="null"
(picked)="onPicked($event)"
/>Backed by a server
[items] does not have to be a fixed list. Bind [(query)] to read what the user typed, dispatch on (searchChange) — which fires once the query settles, not once per keystroke — and feed the answer back into [items]. Add serverSearch so the built-in substring filter steps aside: a backend that ranks or tolerates typos returns rows whose labels do not contain the query at all, and filtering them again on the client would throw away the good ones. [loading] swaps the empty row for “Searching…”, because a palette that reports “No results” between every keystroke and its answer is stating something that is not true yet.
<wr-command-palette
[items]="results()"
[(query)]="query"
[loading]="loading()"
serverSearch
[debounceMs]="250"
(searchChange)="search($event)"
/>API
| Name | Description | Type | Default |
|---|---|---|---|
paletteLabel | Accessible name. Falls back to commandPalette.label, then 'Command palette'. | string | null | null |
items | Items shown in the palette. | readonly WrCommandItem[] | [] |
open | Controlled open state (two-way bindable). | boolean | false |
trigger | Global hotkey that opens the palette. null disables auto-binding. | WrHotkeySpec | null | 'mod+k' |
placeholder | Search input placeholder. Falls back to commandPalette.placeholder. | string | null | null |
emptyText | Text shown when no items match. Falls back to commandPalette.noResults. | string | null | null |
loadingText | Overrides the commandPalette.loading catalog string, shown instead of {@link emptyText} while {@link loading} is true. | string | null | null |
closeOnPick | Auto-close on (picked). Set false to keep open. | boolean | true |
responsive | Present the palette full-screen on small viewports instead of a centred modal. undefined follows the app-wide provideWrResponsiveOverlays() setting; true/false overrides it. The palette docks to the top (it auto-focuses its input, so the on-screen keyboard stays clear). | boolean | undefined | undefined |
(picked) | Fires when the user commits an item (Enter / click). | WrCommandItem | — |
query | The search text. Two-way, so a host can seed it, clear it, or read what the user is looking for — the palette used to keep this to itself, which is what made it impossible to back with anything but a static items array. Writing it does NOT re-run a host's search on its own: the debounced {@link searchChange} output is what fires, on exactly the cadence {@link debounceMs} sets. | string | '' |
serverSearch | The item list is already scoped to the query upstream — skip the built-in client-side filter. Set it when [items] is fed from a server via the {@link searchChange} output. Without this, a backend that ranks or tolerates typos (returning rows whose labels do not literally contain the query) would have those rows hidden again on the client — the same rule wr-select follows, and the same reason. | boolean | false |
loading | A host-driven search is in flight. Swaps the empty row for {@link loadingText} — an async palette that says "No results" between every keystroke and its answer is stating something false. | boolean | false |
debounceMs | How long the query must settle before {@link searchChange} fires, in ms. A search request per keystroke is what exhausts a metered search backend: typing select is six requests at 0 and roughly one at 250. | number | 250 |
(searchChange) | The settled query. This is the hook for a server-backed palette: dispatch on it, then feed the result into [items] with [serverSearch] set. Fires on the {@link debounceMs} cadence, NOT per keystroke — queryChange is the per-keystroke signal if that is what you want. | string | — |
Types
| Name | Description | Type | Default |
|---|---|---|---|
WrCommandItem | { id, label, description?, group?, icon?, keywords?, shortcut?, action? } | interface | — |