SearchPalette
Cmd/Ctrl+K search & command palette.
A keyboard-first search palette: a modal combobox over a grouped listbox, built on the native `<dialog>` like Modal. Bind `open` (the default `mod+k` shortcut toggles it globally), hand it `items`, and it filters, groups and keyboard-navigates them — focus stays on the input the whole time (`aria-activedescendant` pattern). Items with `href` render as real anchors, so Enter and click take the exact same native navigation path your router already intercepts. For async/external search, set `filter={false}` and bind `query`: the palette renders exactly the pre-filtered `items` you pass.
Demo
Usage
<script>
import { SearchPalette } from 'dssoca';
let open = $state(false);
</script>
<!-- opens with mod+K — Cmd+K on macOS, Ctrl+K elsewhere (or set bind:open yourself) -->
<SearchPalette
bind:open
items={[
{ id: 'home', label: 'Home', href: '/', group: 'Pages' },
{ id: 'tokens', label: 'Tokens', href: '/tokens', group: 'Pages', keywords: ['colors'] },
{ id: 'theme', label: 'Toggle theme' },
]}
onselect={(item) => item.id === 'theme' && toggleTheme()}
/>Built on the native `<dialog>` (focus trap, Esc, top layer, `::backdrop`) with the ARIA APG combobox pattern: focus stays on the input and options are referenced via `aria-activedescendant` — never DOM-focused. ArrowUp/Down wrap, PageUp/PageDown jump to first/last (Home/End keep their native caret behavior), Tab is swallowed while open, disabled items are skipped. The component is generic (`T extends SearchPaletteItem`), so your extra item fields flow through `onselect` and the `item` snippet typed. The toggle chord is registry-backed (id `ss:search-palette`): it lists in ShortcutsHelp, obeys `shortcuts.setEnabled()` / `shortcuts.remap()`, and fires on the platform modifier only (Cmd+K on macOS, Ctrl+K elsewhere — remap to `'mod+k, ctrl+k'` if you want both). Anything else bound to `mod+k` (another palette, a Topbar with `onCommand`) collides deterministically — the last one mounted wins — so give the chord to exactly one (`shortcut={false}` on the rest). Size controls the panel width only. Zero border-radius.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Bindable. Whether the palette is shown; syncs to the native dialog. |
items | SearchPaletteItem[] | — | Required. Entries: `{ id, label, hint?, href?, group?, keywords?, disabled? }`. `href` items render as real `<a>`s (native routing); `group` buckets render in first-appearance order, ungrouped items first. |
query | string | '' | Bindable. The search text — bind it when you own filtering (`filter={false}`). |
filter | boolean | true | Internal case/diacritic-insensitive substring filter over `label` + `keywords`. Set false and pass pre-filtered `items` for external/async search. |
shortcut | 'mod+k' | false | 'mod+k' | Global toggle chord (Cmd+K on macOS, Ctrl+K elsewhere — the platform modifier only). Registered through the shortcut registry as `ss:search-palette`. `false` disables it. |
placeholder | string | 'Search…' | Input placeholder. |
emptyText | string | 'No results' | No-results text (when the `empty` snippet is absent). |
footerText | string | false | '↑↓ navigate · ↵ open · esc close' | Footer hint row; `false` hides it. A `footer` snippet replaces it entirely. |
resetOnClose | boolean | true | Clear the query and selection when the palette closes. |
aria-label | string | 'Search' | Accessible name for the dialog, input and listbox. |
onselect | (item: T) => void | boolean | — | Called with the chosen item on Enter/click. Return `false` to keep the palette open. |
onopen | () => void | — | Called after the palette opens. |
onclose | () => void | — | Called after the palette closes (Esc, backdrop, or selection). |
item | Snippet<[T, { active: boolean }]> | — | Replaces the default row body (label + hint). Must render non-interactive content — it lives inside `role="option"` and focus never leaves the input. |
empty | Snippet<[string]> | — | Replaces the default no-results state; receives the current query. |
footer | Snippet | — | Replaces the footer text row. |
size | 'sm' | 'md' | 'lg' | — | Per-instance size override; inherits the ancestor `data-size-variant` when unset. |