<script setup>
import { computed, ref } from "vue";
import { Button } from "@dicehub/kappa/components/button";
import { CommandPalette } from "@dicehub/kappa/components/command-palette";
const open = ref(false);
const query = ref("");
const selected = ref("");
const groups = [
{
label: "Commands",
items: [
{ id: "create", title: "Create new project" },
{ id: "settings", title: "Open settings" },
{ id: "search", title: "Search files" },
],
},
{
label: "Pages",
items: [
{ id: "home", title: "Home" },
{ id: "dashboard", title: "Dashboard" },
],
},
];
const filteredGroups = computed(() => groups
.map((group) => ({
...group,
items: group.items.filter((item) =>
item.title.toLowerCase().includes(query.value.toLowerCase()),
),
}))
.filter((group) => group.items.length));
const flatten = (items) => items.flatMap((group) => group.items);
const title = (item) => item.title;
const select = (item) => {
selected.value = item.title;
query.value = "";
open.value = false;
};
</script>
<template>
<Button @click="open = true">Open Command Palette</Button>
<p v-if="selected">Last selected: {{ selected }}</p>
<CommandPalette.Root
v-model:open="open"
v-model:value="query"
:filter="false"
:get-selectable-items="flatten"
:item-to-string-value="title"
:items="filteredGroups"
@select="select"
>
<CommandPalette.Input placeholder="Type a command or search..." />
<CommandPalette.List>
<CommandPalette.Results v-slot="{ item: group }">
<CommandPalette.Group :items="group.items">
<CommandPalette.GroupLabel>{{ group.label }}</CommandPalette.GroupLabel>
<CommandPalette.Items v-slot="{ item }">
<CommandPalette.Item :value="item">{{ item.title }}</CommandPalette.Item>
</CommandPalette.Items>
</CommandPalette.Group>
</CommandPalette.Results>
<CommandPalette.Empty>No commands found.</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span><kbd>↑↓</kbd> Navigate</span>
<span><kbd>Enter</kbd> Select</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</template>Installation
Barrel
import { CommandPalette } from "@dicehub/kappa";Granular
import { CommandPalette } from "@dicehub/kappa/components/command-palette";Usage
CommandPalette is a compound component built on Ark UI Dialog and Combobox primitives. Pass a flat item array for built-in filtering, or control the query and flatten visible groups with getSelectableItems.
<script setup>
import { ref } from "vue";
import { CommandPalette } from "@dicehub/kappa/components/command-palette";
const open = ref(false);
const commands = [
{ id: "create", title: "Create project" },
{ id: "settings", title: "Open settings" },
];
</script>
<template>
<button @click="open = true">Open</button>
<CommandPalette.Root v-model:open="open" :items="commands" @select="open = false">
<CommandPalette.Input placeholder="Search..." />
<CommandPalette.List>
<CommandPalette.Results v-slot="{ item }">
<CommandPalette.Item :value="item">{{ item.title }}</CommandPalette.Item>
</CommandPalette.Results>
<CommandPalette.Empty>No results.</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</template>Composition
CommandPalette.Root
└── CommandPalette.Dialog
└── CommandPalette.Panel
├── CommandPalette.Input
├── CommandPalette.List
│ ├── CommandPalette.Empty
│ ├── CommandPalette.Loading
│ └── CommandPalette.Results
│ └── CommandPalette.Group
│ ├── CommandPalette.GroupLabel
│ └── CommandPalette.Items
│ └── CommandPalette.Item
└── CommandPalette.FooterRoot provides the usual modal command surface. Dialog and Panel are also public so an application can keep one modal mounted while it swaps command levels or result sources.
<CommandPalette.Root>
<CommandPalette.Dialog>
<CommandPalette.Panel>
<CommandPalette.Input />
<CommandPalette.List>
<CommandPalette.Empty />
<CommandPalette.Results>
<CommandPalette.Group>
<CommandPalette.GroupLabel />
<CommandPalette.Items>
<CommandPalette.Item />
</CommandPalette.Items>
</CommandPalette.Group>
</CommandPalette.Results>
</CommandPalette.List>
<CommandPalette.Footer />
</CommandPalette.Panel>
</CommandPalette.Dialog>
</CommandPalette.Root>Flat lists
Render Results directly. Object items use itemToStringValue when their searchable label is not one of the default keys.
Groups and collections
Render Group, GroupLabel, and Items for each visible group. Flatten only visible group items for keyboard navigation.
Single activation
Unlike a multi-select Combobox, a command palette runs one highlighted command. Chips, multiple selection, and a clear-selection control do not apply.
Examples
With Grouped Items
Group related commands with labels. The first enabled command is highlighted automatically, and disabled commands remain visible but are skipped.
<script setup>
import { computed, ref } from "vue";
import { Button } from "@dicehub/kappa/components/button";
import { CommandPalette } from "@dicehub/kappa/components/command-palette";
const open = ref(false);
const query = ref("");
const selected = ref("");
const groups = [
{
label: "Commands",
items: [
{ id: "create", title: "Create new project" },
{ id: "settings", title: "Open settings" },
{ id: "search", title: "Search files" },
],
},
{
label: "Pages",
items: [
{ id: "home", title: "Home" },
{ id: "dashboard", title: "Dashboard" },
],
},
];
const filteredGroups = computed(() => groups
.map((group) => ({
...group,
items: group.items.filter((item) =>
item.title.toLowerCase().includes(query.value.toLowerCase()),
),
}))
.filter((group) => group.items.length));
const flatten = (items) => items.flatMap((group) => group.items);
const title = (item) => item.title;
const select = (item) => {
selected.value = item.title;
query.value = "";
open.value = false;
};
</script>
<template>
<Button @click="open = true">Open Command Palette</Button>
<p v-if="selected">Last selected: {{ selected }}</p>
<CommandPalette.Root
v-model:open="open"
v-model:value="query"
:filter="false"
:get-selectable-items="flatten"
:item-to-string-value="title"
:items="filteredGroups"
@select="select"
>
<CommandPalette.Input placeholder="Type a command or search..." />
<CommandPalette.List>
<CommandPalette.Results v-slot="{ item: group }">
<CommandPalette.Group :items="group.items">
<CommandPalette.GroupLabel>{{ group.label }}</CommandPalette.GroupLabel>
<CommandPalette.Items v-slot="{ item }">
<CommandPalette.Item :value="item">{{ item.title }}</CommandPalette.Item>
</CommandPalette.Items>
</CommandPalette.Group>
</CommandPalette.Results>
<CommandPalette.Empty>No commands found.</CommandPalette.Empty>
</CommandPalette.List>
<CommandPalette.Footer>
<span><kbd>↑↓</kbd> Navigate</span>
<span><kbd>Enter</kbd> Select</span>
</CommandPalette.Footer>
</CommandPalette.Root>
</template>Simple Flat List
Use a flat array for a compact command list. Built-in filtering uses title, label, name, or value text.
<script setup>
import { ref } from "vue";
import { CommandPalette } from "@dicehub/kappa/components/command-palette";
const open = ref(false);
const actions = [
{ id: "copy", title: "Copy" },
{ id: "paste", title: "Paste" },
{ id: "cut", title: "Cut" },
{ id: "delete", title: "Delete" },
];
</script>
<template>
<button @click="open = true">Open Simple Palette</button>
<CommandPalette.Root v-model:open="open" :items="actions" @select="open = false">
<CommandPalette.Input placeholder="Search actions..." />
<CommandPalette.List>
<CommandPalette.Results v-slot="{ item }">
<CommandPalette.Item :value="item">{{ item.title }}</CommandPalette.Item>
</CommandPalette.Results>
<CommandPalette.Empty>No actions found.</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</template>Loading State
Replace results with an accessible loading status while commands load from a local or remote index.
<script setup>
import { ref } from "vue";
import { CommandPalette } from "@dicehub/kappa/components/command-palette";
const open = ref(false);
const loading = ref(false);
const items = ref([]);
const openPalette = async () => {
open.value = true;
loading.value = true;
items.value = await loadCommands();
loading.value = false;
};
</script>
<template>
<button @click="openPalette">Open with Loading</button>
<CommandPalette.Root v-model:open="open" :items="loading ? [] : items">
<CommandPalette.Input placeholder="Search..." />
<CommandPalette.List>
<CommandPalette.Loading v-if="loading" />
<template v-else>
<CommandPalette.Results v-slot="{ item }">
<CommandPalette.Item :value="item">{{ item.title }}</CommandPalette.Item>
</CommandPalette.Results>
<CommandPalette.Empty>No results found.</CommandPalette.Empty>
</template>
</CommandPalette.List>
</CommandPalette.Root>
</template>Disabling Browser Autocomplete
Forward standard input and password-manager attributes when browser overlays would compete with palette results.
<CommandPalette.Input
autocomplete="off"
autocapitalize="none"
autocorrect="off"
data-1p-ignore="true"
data-lpignore="true"
placeholder="Search commands..."
:spellcheck="false"
/>ResultItem with Breadcrumbs
Use ResultItem for object items that need breadcrumbs, descriptions, icons, match highlights, or destination cues.
<script setup>
import { ref } from "vue";
import { CommandPalette } from "@dicehub/kappa/components/command-palette";
const open = ref(false);
const results = [
{ id: "button", title: "Button", breadcrumbs: ["Components"] },
{ id: "dialog", title: "Dialog", breadcrumbs: ["Components"] },
{ id: "header", title: "Page Header", breadcrumbs: ["Blocks"] },
];
</script>
<template>
<button @click="open = true">Open with ResultItem</button>
<CommandPalette.Root v-model:open="open" :items="results" @select="open = false">
<CommandPalette.Input placeholder="Search documentation..." />
<CommandPalette.List>
<CommandPalette.Results v-slot="{ item }">
<CommandPalette.ResultItem
:breadcrumbs="item.breadcrumbs"
:description="item.description"
:title="item.title"
:value="item"
/>
</CommandPalette.Results>
<CommandPalette.Empty>No pages found.</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette.Root>
</template>Component Parts
CommandPalette.Root
Combines Dialog and Panel and owns controlled or uncontrolled state.
CommandPalette.Dialog
Provides the modal layer, focus trap, dismissal, portal, and focus restoration.
CommandPalette.Panel
Provides the Combobox collection, filtering, highlighting, and selection state.
CommandPalette.Input
Forwards input attributes and handles search and keyboard activation.
CommandPalette.List
Contains the scrollable result area.
CommandPalette.Results
Iterates the visible top-level items or groups.
CommandPalette.Group
Groups related commands and supplies items to Items.
CommandPalette.GroupLabel
Labels a command group.
CommandPalette.Items
Iterates commands inside the nearest Group.
CommandPalette.Item
Provides one selectable or disabled command.
CommandPalette.ResultItem
Adds breadcrumbs, description, icon, highlights, and destination cues.
CommandPalette.HighlightedText
Marks inclusive match ranges without using HTML injection.
CommandPalette.Empty
Displays the no-results state.
CommandPalette.Loading
Displays an accessible loading status.
CommandPalette.Footer
Contains keyboard help or related status information.
Accessibility
- Dialog traps focus, blocks background interaction, and restores focus after dismissal.
- The search input receives initial focus and has a configurable accessible name.
- Disabled commands remain perceivable and are skipped during keyboard navigation.
- Empty and Loading expose complete no-result and progress states.
- Overlay and result motion honor
prefers-reduced-motion.
API Reference
Root
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | - | Controlled dialog state; supports v-model:open. |
defaultOpen | boolean | false | Initial dialog state for uncontrolled use. |
value | string | - | Controlled search query; supports v-model:value. |
defaultValue | string | "" | Initial uncontrolled search query. |
items | T[] | [] | Flat results or top-level groups rendered by Results. |
getSelectableItems | (items: T[]) => T[] | identity | Flattens grouped data into keyboard-selectable items. |
itemToStringValue | (item: T) => string | title / label / name / value | Maps an object item to searchable text. |
itemToValue | (item: T) => string | id / value / title / label | Maps an item to its stable Ark UI value. |
isItemDisabled | (item: T) => boolean | item.disabled | Marks commands unavailable and skips them during navigation. |
filter | false | (item, query) => boolean | contains text | Filters flat items. Use false for externally filtered groups. |
ariaLabel | string | "Command palette" | Accessible name for the dialog. |
modal | boolean | true | Controls focus trapping and outside interaction. |
ResultItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | T | - | Item passed to selection events. |
title | string | - | Primary result label. |
breadcrumbs | string[] | [] | Hierarchy rendered before the title. |
description | string | - | Supporting result text. |
titleHighlights | [number, number][] | [] | Inclusive title match ranges. |
breadcrumbHighlights | [number, number][][] | [] | Inclusive ranges for each breadcrumb. |
showArrow | boolean | true | Shows the destination arrow. |
external | boolean | false | Shows an external-destination cue. |
nonInteractive | boolean | false | Renders rich result content without activation. |
Events
| Event | Payload | Description |
|---|---|---|
update:open | boolean | Updates controlled dialog state. |
openChange | boolean | Reports each dialog state change. |
update:value | string | Updates the controlled search query. |
valueChange | string | Reports each search query change. |
select | item, { newTab, event? } | Reports activation and modified-key intent. |
itemHighlighted | item, { index, reason } | Reports keyboard, pointer, and reset highlights. |
close | void | Reports closure after dismissal or a state change. |