CommandMenu

CommandMenu renders a searchable option list with keyboard navigation, grouping, and async loading.

It executes one command at a time and closes, without retaining a selection. Selection bindings (value), selection limits, chip controls, user-created options, bulk selection, and pasted option lists belong to MultiSelect. Use on_execute={({ action }) => …} to observe commands invoked by menu selection or global shortcut, and on_activate to track keyboard navigation. Replace the former on_add callback with on_execute; it reports invocation, without waiting for asynchronous actions to finish.

Search, grouping, loading, and styling props share OptionListProps with MultiSelect. The labels prop accepts loading_more, loading_failed, retry, and group overrides to localize status messages and group buttons. placeholder, no_matching_options_msg, and default_disabled_title control their respective text separately.

svelte<script lang="ts">
  import { goto } from '$app/navigation'
  import { resolve } from '$app/paths'
  import type { Pathname } from '$app/types'
  import { CommandMenu } from 'svelte-widgets'
  import { demo_pages } from '../../index'

  const resolve_path = resolve as (path: Pathname) => string
  const actions = demo_pages.map((route) => ({
    id: route,
    label: route,
    action: () => goto(resolve_path(route)),
  }))
</script>

<CommandMenu {actions} triggers={[`n`]} />

PageSearch

PageSearch wraps CommandMenu with full-text search over statically generated pages. fallback_actions are matched locally on every keystroke, so known routes show up without waiting on the index. Failed index, search, or result downloads show a Retry button. Successful results remain visible, including those from a partially failed batch; Retry downloads only failed results and restores their search order. Install pagefind as a development dependency, then index the rendered site after the application build. Run this script before previewing or deploying:

{
  "scripts": {
    "build:site": "vite build && pagefind --site build"
  }
}

The navigate callback receives the selected result’s query, label, and description as its second argument. A persistent layout can carry the query across client-side navigation and apply highlight_matches to the destination content:

<script lang="ts">
  import { afterNavigate, goto } from '$app/navigation'
  import { highlight_matches } from 'svelte-widgets/attachments'
  import type { PageSearchNavigateDetails } from 'svelte-widgets'

  let highlight_query = $state(``)

  const navigate = async (url: string, { query }: PageSearchNavigateDetails) => {
    await goto(url)
    highlight_query = ``
    queueMicrotask(() => (highlight_query = query))
  }

  afterNavigate(() => (highlight_query = ``))
</script>

<main
  {@attach highlight_matches({
    query: highlight_query,
    css_class: `site-search-match`,
    duration_ms: 8000,
  })}
>
  ...
</main>

<style>
  :global(::highlight(site-search-match)) {
    background: gold;
    color: inherit;
  }
</style>

See the highlight_matches attachments demo for options and effects. Stemmed Pagefind results may have no exact substring.

Shortcuts, Descriptions & Recent Actions

Actions can carry a description, metadata, badge, keywords, shortcut, and disabled state. The default filter searches all visible fields plus keywords and supports multiple terms. Shortcuts render as -style key hints and trigger globally while the menu is closed unless global_shortcuts={false}. Pass recent_actions_key to persist triggered actions to localStorage and rank them first when the menu reopens.

Open with cmd/ctrl+p or press ctrl+shift+l / ctrl+shift+u anywhere on this page. Last triggered: none

svelte<script lang="ts">
  import { CommandMenu } from 'svelte-widgets'
  import { apply_theme_mode, theme, THEME_MODE_CYCLE } from 'svelte-widgets/theme'

  let last_triggered = $state(``)

  const actions = [
    {
      id: `Toggle theme`,
      label: `Toggle theme`,
      description: `Cycle light, system and dark modes`,
      metadata: [`Appearance`],
      badge: `Setting`,
      keywords: [`color scheme`],
      shortcut: `ctrl+shift+l`,
      action: () => {
        const next_mode = THEME_MODE_CYCLE[theme.mode]
        apply_theme_mode(next_mode)
        last_triggered = `Theme: ${next_mode}`
      },
    },
    {
      id: `Copy page URL`,
      label: `Copy page URL`,
      description: `Copy the current address to the clipboard`,
      shortcut: `ctrl+shift+u`,
      action: (label: string) => (last_triggered = label),
    },
    {
      id: `Open settings`,
      label: `Open settings`,
      action: (label: string) => (last_triggered = label),
    },
  ]
</script>

<CommandMenu
  {actions}
  triggers={[`p`]}
  recent_actions_key="demo-recent-actions"
  placeholder="Recently used actions float to the top..."
/>
<p>
  Open with <kbd>cmd/ctrl+p</kbd> or press <kbd>ctrl+shift+l</kbd> /
  <kbd>ctrl+shift+u</kbd> anywhere on this page. Last triggered:
  <strong>{last_triggered || `none`}</strong>
</p>

Actions require a stable, unique, nonempty string id, including across static actions and loaded pages. Numeric, empty, whitespace-only, and duplicate IDs are rejected. Labels may repeat or change; exact string IDs identify actions and persisted recents. ActionMenu uses the same identity rules.