CommandMenu
A modal search palette over a flat vocabulary of items — grouped results, diacritic-insensitive filtering, match highlighting, and full keyboard navigation with focus that never leaves the search field.
Preview
<script lang="ts">
import { CommandMenu, type CommandItem } from "fancy-ui-svelte";
import { Button } from "fancy-ui-svelte";
let open = $state(false);
let picked = $state("");
const items: CommandItem[] = [
{ id: "button", label: "Button", meta: "Actions" },
{ id: "icon-button", label: "Icon Button", meta: "Actions" },
{ id: "rainbow-button", label: "Rainbow Button", meta: "Buttons" },
{ id: "settings", label: "Settings" },
];
function handleSelect(item: CommandItem) {
picked = item.label;
}
</script>
<div class="flex flex-col items-start gap-3">
<Button onclick={() => (open = true)}>Open command menu</Button>
{#if picked}
<p class="text-muted-foreground text-[12px]">Last picked: {picked}</p>
{/if}
</div>
<CommandMenu bind:open {items} onSelect={handleSelect} />Installation
Usage
<script lang="ts">
import { CommandMenu } from 'fancy-ui-svelte';
</script>
<CommandMenu />Examples
Basic Usage
<script lang="ts">
import { CommandMenu, type CommandItem } from "$lib/fancy-ui/command-menu";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
let picked = $state("");
const items: CommandItem[] = [
{ id: "button", label: "Button", meta: "Actions" },
{ id: "icon-button", label: "Icon Button", meta: "Actions" },
{ id: "rainbow-button", label: "Rainbow Button", meta: "Buttons" },
{ id: "settings", label: "Settings" },
];
function handleSelect(item: CommandItem) {
picked = item.label;
}
</script>
<div class="flex flex-col items-start gap-3">
<Button onclick={() => (open = true)}>Open command menu</Button>
{#if picked}
<p class="text-muted-foreground text-[12px]">Last picked: {picked}</p>
{/if}
</div>
<CommandMenu bind:open {items} onSelect={handleSelect} />Grouped Items
Items with no group render first, then each group under its own heading.
<script lang="ts">
import { CommandMenu, type CommandItem } from "$lib/fancy-ui/command-menu";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
// Items with no `group` render first, ungrouped — "Search everything"
// here. Everything else buckets under the uppercase heading matching its
// own `group`, in the order that group name first appears in the list.
const items: CommandItem[] = [
{ id: "search", label: "Search everything" },
{ id: "new-doc", label: "New document", group: "Create", meta: "Document" },
{ id: "new-folder", label: "New folder", group: "Create", meta: "Folder" },
{ id: "profile", label: "Go to profile", group: "Navigate" },
{ id: "billing", label: "Go to billing", group: "Navigate" },
];
</script>
<Button onclick={() => (open = true)}>Open command menu</Button>
<CommandMenu bind:open {items} label="Jump to" />Empty State
The message shown when nothing matches the query.
<script lang="ts">
import { CommandMenu, type CommandItem } from "$lib/fancy-ui/command-menu";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
const items: CommandItem[] = [
{ id: "button", label: "Button" },
{ id: "icon-button", label: "Icon Button" },
{ id: "rainbow-button", label: "Rainbow Button" },
];
</script>
<!--
Opens on a click, with the query pre-filled to something no item matches, so
the empty state is one interaction away rather than a keystroke away.
It deliberately does NOT mount already-open. `CommandMenu` is modal and
portalled to `document.body`, so an example rendered with `open` set does
not appear inside the docs page's preview box the way every non-modal
example does — it covers the entire page, traps focus and locks scrolling
from the moment the route mounts, before the reader has scrolled anywhere
near the Examples section. Whatever the example is meant to demonstrate,
that is what a visitor sees first.
-->
<Button onclick={() => (open = true)}>Show the empty state</Button>
<CommandMenu
bind:open
{items}
query="zzz-no-match"
emptyMessage="No components match “zzz-no-match”."
/>Custom Filter
Replacing the default filter entirely, e.g. with a prefix match.
<script lang="ts">
import { CommandMenu, type CommandItem } from "$lib/fancy-ui/command-menu";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
const items: CommandItem[] = [
{ id: "button", label: "Button" },
{ id: "badge", label: "Badge" },
{ id: "banner", label: "Banner" },
{ id: "icon-button", label: "Icon Button" },
];
// Replaces the default filter entirely — only a prefix match on the
// label counts, so typing "ba" surfaces "Badge" and "Banner" but not
// "Icon Button", even though "icon-button" never appears anywhere here.
function prefixFilter(item: CommandItem, query: string): boolean {
return item.label.toLowerCase().startsWith(query.toLowerCase());
}
</script>
<Button onclick={() => (open = true)}>Open command menu</Button>
<CommandMenu bind:open {items} filter={prefixFilter} />With Keywords
Matching on keywords that never appear in the visible label.
<script lang="ts">
import { CommandMenu, type CommandItem } from "$lib/fancy-ui/command-menu";
import { Button } from "$lib/fancy-ui/button";
let open = $state(false);
// `keywords` extend what the default filter matches against without
// ever being shown — typing "invoice" or "plan" still finds "Billing",
// and typing "shortcut" still finds "Keyboard Shortcuts".
const items: CommandItem[] = [
{ id: "billing", label: "Billing", meta: "Settings", keywords: ["invoice", "plan", "payment"] },
{
id: "shortcuts",
label: "Keyboard Shortcuts",
meta: "Help",
keywords: ["hotkeys", "bindings"],
},
{ id: "theme", label: "Appearance", meta: "Settings", keywords: ["dark mode", "light mode"] },
];
</script>
<Button onclick={() => (open = true)}>Open command menu</Button>
<CommandMenu bind:open {items} placeholder="Try “invoice” or “hotkeys”…" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Whether the menu is open; bindable |
onOpenChange | (open: boolean) => void | - | Fires whenever open changes — Escape, an outside click, or committing an item |
items * | CommandItem[] | - | The full, unfiltered vocabulary |
query | string | "" | The current search text; bindable. Reset every time the menu reopens |
onQueryChange | (query: string) => void | - | Fires whenever query changes |
onSelect | (item: CommandItem) => void | - | Called with the committed item, after that item's own onSelect |
placeholder | string | "Search..." | Placeholder for the search field |
emptyMessage | string | "No results" | Shown in place of the list when nothing matches |
label | string | "Command menu" | Accessible name for the dialog and the search field |
filter | (item: CommandItem, query: string) => boolean | - | Overrides the default case- and diacritic-insensitive substring filter entirely |
class | string | - | Additional CSS classes for the panel |
ref | HTMLDivElement | null | null | Bindable reference to the panel element. |
Slots
| Slot | Description |
|---|---|
icon | Rendered before each row's label, given that row's item. Treated as decorative |
empty | Rendered in place of the list when nothing matches, instead of emptyMessage |
Links
Related components
Breadcrumb
A data-driven trail of links with automatic truncation, a decorative separator, and the current page marked for assistive tech.
ContextMenu
A menu that opens at the pointer on right-click, sharing DropdownMenu's item, submenu and keyboard behaviour but anchored to a virtual point instead of a trigger element.
Dock
Icon dock where each item magnifies smoothly as the cursor approaches, sharing pointer position via Svelte context so every DockIcon scales by proximity within a configurable magnification and distance range