<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
import ChartLineIcon from "phosphor-svelte/lib/ChartLineIcon";
import FolderIcon from "phosphor-svelte/lib/FolderIcon";
import GearIcon from "phosphor-svelte/lib/GearIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import MagnifyingGlassIcon from "phosphor-svelte/lib/MagnifyingGlassIcon";
import UsersIcon from "phosphor-svelte/lib/UsersIcon";
const groups = [
{
label: "Commands",
items: [
{ value: "create-new-project", title: "Create New Project", icon: FolderIcon },
{ value: "open-settings", title: "Open Settings", icon: GearIcon },
{ value: "search-files", title: "Search Files", icon: MagnifyingGlassIcon },
],
},
{
label: "Pages",
items: [
{ value: "home", title: "Home", icon: HouseIcon },
{ value: "dashboard", title: "Dashboard", icon: ChartLineIcon },
{ value: "users", title: "Users", icon: UsersIcon },
],
},
];
let open = $state(false);
let search = $state("");
let selectedItem = $state<string | null>(null);
function handleSelect(title: string) {
selectedItem = title;
search = "";
open = false;
}
</script>
<div class="flex flex-col items-start gap-4">
<Button onclick={() => (open = true)}>Open Command Palette</Button>
{#if selectedItem}
<p class="text-sm text-kumo-subtle">
Last selected: <span class="text-kumo-default">{selectedItem}</span>
</p>
{/if}
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Type a command or search..." />
<CommandPalette.List>
{#each groups as group}
<CommandPalette.Group value={group.label}>
<CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
{#each group.items as item}
{@const Icon = item.icon}
<CommandPalette.Item value={item.title} onSelect={() => handleSelect(item.title)}>
<span class="flex items-center gap-3">
<span class="text-kumo-subtle"><Icon size={16} /></span>
<span>{item.title}</span>
</span>
</CommandPalette.Item>
{/each}
</CommandPalette.Group>
{/each}
<CommandPalette.Empty>No commands found</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span class="flex items-center gap-2">
<kbd class="rounded border border-kumo-hairline bg-kumo-base px-1.5 py-0.5 text-[10px]">↑↓</kbd>
<span>Navigate</span>
</span>
<span class="flex items-center gap-2">
<kbd class="rounded border border-kumo-hairline bg-kumo-base px-1.5 py-0.5 text-[10px]">↵</kbd>
<span>Select</span>
</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</CommandPalette.Dialog>
</div>
Import
import * as CommandPalette from "kumo-svelte/components/command-palette"; Usage
CommandPalette is built on Bits UI’s Command primitive. Use CommandPalette.Dialog when the palette should open in a modal dialog, or use CommandPalette directly for an inline command surface.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
const items = ["Create Project", "Open Settings"];
let open = $state(false);
let search = $state("");
</script>
<Button onclick={() => (open = true)}>Open</Button>
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Search..." />
<CommandPalette.List>
{#each items as item}
<CommandPalette.Item value={item} onSelect={() => (open = false)}>
{item}
</CommandPalette.Item>
{/each}
<CommandPalette.Empty>No results</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</CommandPalette.Dialog> Keyboard Navigation
Built-in keyboard navigation is provided automatically by the Command primitive:
| Keys | Description |
|---|---|
↑ / ↓ | Move highlight between items. |
Enter | Select highlighted item. |
Escape | Close the dialog when used inside CommandPalette.Dialog. |
Examples
With Grouped Items
Group related commands together with labels.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
import ChartLineIcon from "phosphor-svelte/lib/ChartLineIcon";
import FolderIcon from "phosphor-svelte/lib/FolderIcon";
import GearIcon from "phosphor-svelte/lib/GearIcon";
import HouseIcon from "phosphor-svelte/lib/HouseIcon";
import MagnifyingGlassIcon from "phosphor-svelte/lib/MagnifyingGlassIcon";
import UsersIcon from "phosphor-svelte/lib/UsersIcon";
const groups = [
{
label: "Commands",
items: [
{ value: "create-new-project", title: "Create New Project", icon: FolderIcon },
{ value: "open-settings", title: "Open Settings", icon: GearIcon },
{ value: "search-files", title: "Search Files", icon: MagnifyingGlassIcon },
],
},
{
label: "Pages",
items: [
{ value: "home", title: "Home", icon: HouseIcon },
{ value: "dashboard", title: "Dashboard", icon: ChartLineIcon },
{ value: "users", title: "Users", icon: UsersIcon },
],
},
];
let open = $state(false);
let search = $state("");
let selectedItem = $state<string | null>(null);
function handleSelect(title: string) {
selectedItem = title;
search = "";
open = false;
}
</script>
<div class="flex flex-col items-start gap-4">
<Button onclick={() => (open = true)}>Open Command Palette</Button>
{#if selectedItem}
<p class="text-sm text-kumo-subtle">
Last selected: <span class="text-kumo-default">{selectedItem}</span>
</p>
{/if}
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Type a command or search..." />
<CommandPalette.List>
{#each groups as group}
<CommandPalette.Group value={group.label}>
<CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
{#each group.items as item}
{@const Icon = item.icon}
<CommandPalette.Item value={item.title} onSelect={() => handleSelect(item.title)}>
<span class="flex items-center gap-3">
<span class="text-kumo-subtle"><Icon size={16} /></span>
<span>{item.title}</span>
</span>
</CommandPalette.Item>
{/each}
</CommandPalette.Group>
{/each}
<CommandPalette.Empty>No commands found</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span class="flex items-center gap-2">
<kbd class="rounded border border-kumo-hairline bg-kumo-base px-1.5 py-0.5 text-[10px]">↑↓</kbd>
<span>Navigate</span>
</span>
<span class="flex items-center gap-2">
<kbd class="rounded border border-kumo-hairline bg-kumo-base px-1.5 py-0.5 text-[10px]">↵</kbd>
<span>Select</span>
</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</CommandPalette.Dialog>
</div>
Simple Flat List
For simpler use cases, use a flat array of items without grouping.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
const items = ["Copy", "Paste", "Cut", "Delete", "Select All"];
let open = $state(false);
let search = $state("");
</script>
<div>
<Button onclick={() => (open = true)}>Open Simple Palette</Button>
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Search actions..." />
<CommandPalette.List>
{#each items as item}
<CommandPalette.Item value={item} onSelect={() => (open = false)}>
{item}
</CommandPalette.Item>
{/each}
<CommandPalette.Empty>No actions found</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</CommandPalette.Dialog>
</div>
Loading State
Show a loading spinner while fetching results.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
const groups = [
{ label: "Commands", items: ["Create New Project", "Open Settings", "Search Files"] },
{ label: "Pages", items: ["Home", "Dashboard", "Users"] },
];
let open = $state(false);
let loading = $state(false);
let search = $state("");
function handleOpen() {
open = true;
loading = true;
setTimeout(() => {
loading = false;
}, 1500);
}
</script>
<div>
<Button onclick={handleOpen}>Open with Loading</Button>
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Search..." />
<CommandPalette.List>
{#if loading}
<CommandPalette.Loading />
{:else}
{#each groups as group}
<CommandPalette.Group value={group.label}>
<CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
{#each group.items as item}
<CommandPalette.Item value={item} onSelect={() => (open = false)}>
{item}
</CommandPalette.Item>
{/each}
</CommandPalette.Group>
{/each}
<CommandPalette.Empty>No results found</CommandPalette.Empty>
{/if}
</CommandPalette.List>
</CommandPalette.Root>
</CommandPalette.Dialog>
</div>
Disabling Browser Autocomplete
Pass standard HTML input attributes like autocomplete to suppress browser autocomplete overlays.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
const groups = [
{ label: "Commands", items: ["Create New Project", "Open Settings", "Search Files"] },
{ label: "Pages", items: ["Home", "Dashboard", "Users"] },
];
let open = $state(false);
let search = $state("");
</script>
<div class="flex flex-col items-start gap-4">
<Button onclick={() => (open = true)}>Open Palette (No Autocomplete)</Button>
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Search commands..." autocomplete="off" />
<CommandPalette.List>
{#each groups as group}
<CommandPalette.Group value={group.label}>
<CommandPalette.GroupLabel>{group.label}</CommandPalette.GroupLabel>
{#each group.items as item}
<CommandPalette.Item
value={item}
onSelect={() => {
search = "";
open = false;
}}
>
{item}
</CommandPalette.Item>
{/each}
</CommandPalette.Group>
{/each}
<CommandPalette.Empty>No commands found</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</CommandPalette.Dialog>
</div>
ResultItem with Breadcrumbs
Use CommandPalette.ResultItem for rich items with breadcrumbs, icons, and optional text highlighting.
<script lang="ts">
import { Button } from "kumo-svelte/components/button";
import * as CommandPalette from "kumo-svelte/components/command-palette";
import FileIcon from "phosphor-svelte/lib/FileIcon";
const results = [
{ value: "button", title: "Button", breadcrumbs: ["Components"] },
{ value: "dialog", title: "Dialog", breadcrumbs: ["Components"] },
{ value: "page-header", title: "Page Header", breadcrumbs: ["Blocks"] },
];
let open = $state(false);
let search = $state("");
</script>
{#snippet fileIcon()}
<FileIcon size={16} />
{/snippet}
<div>
<Button onclick={() => (open = true)}>Open with ResultItem</Button>
<CommandPalette.Dialog bind:open>
<CommandPalette.Root bind:value={search}>
<CommandPalette.Input placeholder="Search documentation..." />
<CommandPalette.List>
{#each results as result}
<CommandPalette.ResultItem
value={result.title}
title={result.title}
breadcrumbs={result.breadcrumbs}
icon={fileIcon}
onSelect={() => (open = false)}
/>
{/each}
<CommandPalette.Empty>No pages found</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span class="flex items-center gap-2">
<kbd class="rounded border border-kumo-hairline bg-kumo-base px-1.5 py-0.5 text-[10px]">↑↓</kbd>
<span>Navigate</span>
</span>
<span class="flex items-center gap-2">
<kbd class="rounded border border-kumo-hairline bg-kumo-base px-1.5 py-0.5 text-[10px]">⌘↵</kbd>
<span>Open in new tab</span>
</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</CommandPalette.Dialog>
</div>
API Reference
CommandPalette
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| filter | (value: string, search: string, keywords?: string[]) => number | — |
| label | string | "Command palette" |
| loop | boolean | — |
| onValueChange | (value: string) => void | — |
| shouldFilter | boolean | — |
| value | string | "" |
CommandPalette.Dialog
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| onOpenChange | (open: boolean) => void | — |
| open | boolean | false |
CommandPalette.Input
| Prop | Type | Default |
|---|---|---|
| class | string | — |
| leading | Snippet | — |
| trailing | Snippet | — |
CommandPalette.List
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
CommandPalette.Group
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| forceMount | boolean | — |
| value | string | — |
CommandPalette.GroupLabel
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
CommandPalette.Item
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
| disabled | boolean | false |
| forceMount | boolean | — |
| keywords | string[] | — |
| onSelect | () => void | — |
| value | string | — |
CommandPalette.ResultItem
| Prop | Type | Default |
|---|---|---|
| breadcrumbHighlights | HighlightRange[][] | — |
| breadcrumbs | string[] | [] |
| description | string | — |
| external | boolean | false |
| icon | Snippet | — |
| nonInteractive | boolean | false |
| onSelect | () => void | — |
| showArrow | boolean | true |
| title* | string | — |
| titleHighlights | HighlightRange[] | — |
| value | string | — |
HighlightedText
| Prop | Type | Default |
|---|---|---|
| class | string | — |
| highlights | HighlightRange[] | [] |
| text* | string | — |
CommandPalette.Empty
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| forceMount | boolean | — |
CommandPalette.Loading
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| progress | number | — |
CommandPalette.Footer
| Prop | Type | Default |
|---|---|---|
| children | Snippet | — |
| class | string | — |
CommandPalette.Separator
| Prop | Type | Default |
|---|---|---|
| class | string | — |
| forceMount | boolean | — |