dssoca docs
GitHub repository

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

live · storybook Open in Storybook

Demo loads from https://dssoca-storybook.vercel.app/. Run pnpm storybook for it to render locally.

Usage

svelte
<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.

Guide: Making your site keyboard-friendly.

Props

PropTypeDefaultDescription
openbooleanfalseBindable. Whether the palette is shown; syncs to the native dialog.
itemsSearchPaletteItem[]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.
querystring''Bindable. The search text — bind it when you own filtering (`filter={false}`).
filterbooleantrueInternal 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.
placeholderstring'Search…'Input placeholder.
emptyTextstring'No results'No-results text (when the `empty` snippet is absent).
footerTextstring | false'↑↓ navigate · ↵ open · esc close'Footer hint row; `false` hides it. A `footer` snippet replaces it entirely.
resetOnClosebooleantrueClear the query and selection when the palette closes.
aria-labelstring'Search'Accessible name for the dialog, input and listbox.
onselect(item: T) => void | booleanCalled with the chosen item on Enter/click. Return `false` to keep the palette open.
onopen() => voidCalled after the palette opens.
onclose() => voidCalled after the palette closes (Esc, backdrop, or selection).
itemSnippet<[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.
emptySnippet<[string]>Replaces the default no-results state; receives the current query.
footerSnippetReplaces the footer text row.
size'sm' | 'md' | 'lg'Per-instance size override; inherits the ancestor `data-size-variant` when unset.