vue-command-kit
v0.4.0
Published
⌘K — Fast, composable, unstyled command menu for Vue 3
Maintainers
Readme
Features
- Compound component API — Declarative composition with
<Command.Dialog>,<Command.Input>,<Command.Item>, and more - Built-in search — Case-insensitive filtering with keyword matching and result highlighting
- Force render —
forceMountprop keeps items in the DOM when filtered out (for animations) - Keyboard-first — Arrow key navigation, Enter to select, Escape to close, global shortcuts
- Unstyled — Zero CSS opinions; bring your own styles
- TypeScript — Full type inference, exported types, and declaration files
- Zero runtime dependencies — Peer dependency only on
vue ^3.4.0
Demo

Midnight theme · Nested pages · Search filtering · Disabled items
Installation
npm install vue-command-kitQuick Start
With items prop
<script setup lang="ts">
import { ref } from 'vue'
import { Command } from 'vue-command-kit'
import type { CommandItemData } from 'vue-command-kit'
const visible = ref(false)
const items: CommandItemData[] = [
{ value: 'settings', label: 'Open settings', shortcut: '⌘,' },
{ value: 'home', label: 'Go to home', shortcut: '⌘H' },
]
function onSelect(item: CommandItemData) {
console.log('selected:', item.value)
}
</script>
<template>
<button @click="visible = true">Open (⌘K)</button>
<Command.Dialog
:visible="visible"
:items="items"
@update:visible="visible = $event"
@select="onSelect"
/>
</template>With custom slot content
<Command.Dialog :visible="visible" @update:visible="visible = $event">
<template #header>
<Command.Input placeholder="Search..." />
</template>
<template #body>
<Command.List>
<Command.Group heading="Favorites">
<Command.Item value="home" label="Home" />
</Command.Group>
</Command.List>
</template>
</Command.Dialog>API
Components
| Component | Description |
| --------------------- | ---------------------------------------------- |
| <Command.Dialog> | Modal dialog with mask, transition, focus trap |
| <Command.Menu> | Inline command menu (non-modal) with slots |
| <Command.Input> | Search input with keyboard navigation |
| <Command.List> | Scrollable list rendering grouped items |
| <Command.Group> | Group of items with heading |
| <Command.Item> | Single selectable command item |
| <Command.Separator> | Visual separator between groups |
| <Command.Empty> | Shown when no results match |
| <Command.Loading> | Loading indicator |
<Command.Dialog> Props
| Prop | Type | Default | Description |
| ---------------- | ----------------------- | ------------------------------- | ---------------------------------------------- |
| visible | boolean | false | Controlled open state |
| items | CommandItemData[] | [] | Items to display |
| searchQuery | string | — | Search query (v-model:searchQuery) |
| value | string | — | Selected item value (v-model:value) |
| placeholder | string | 'Type a command or search...' | Input placeholder |
| filter | FilterFn | — | Custom filter function |
| loading | boolean | false | Show loading state |
| autoFocus | boolean | true | Auto-focus input on open |
| closeOnSelect | boolean | true | Close dialog after selection |
| shouldFilter | boolean | true | When false, skip built-in filtering |
| loop | boolean | true | When false, keyboard nav stops at boundaries |
| transitionName | string | 'cmdk-dialog' | Custom transition name for the dialog overlay |
| label | string | 'Command menu' | Accessible name for the dialog and combobox |
| container | string \| HTMLElement | 'body' | Teleport target for the dialog |
<Command.Dialog> Events
| Event | Payload | Description |
| -------------------- | ----------------- | --------------------------------- |
| update:visible | boolean | Emitted when visibility changes |
| update:searchQuery | string | Emitted when search query changes |
| update:value | string | Emitted when an item is selected |
| select | CommandItemData | Emitted when an item is selected |
E2E tests
The demo app is covered with Playwright end-to-end tests for the main command palette flows:
- open and close behavior
- keyboard navigation and selection
- search filtering, empty states, loading states, and disabled items
- theme switching through both buttons and command selection
- accessibility contracts such as
aria-activedescendant,aria-selected, focus trap, and automated dialog scanning
Run them locally with:
pnpm test:e2eCommandItemData
interface CommandItemData {
value: string
label?: string
keywords?: string[]
shortcut?: string
group?: string
disabled?: boolean
forceMount?: boolean
icon?: Component | VNode | (() => VNode)
onSelect?: (item: CommandItemData) => void
/** Sub-page items. Selecting this item opens a sub-menu instead of closing */
children?: CommandItemData[]
}useCommandMenu()
The composable provides programmatic control outside of Command.Dialog / Command.Menu.
import { useCommandMenu } from 'vue-command-kit'
const menu = useCommandMenu()
menu.items.value = [...]
menu.open()
menu.close()
menu.toggle()| Return | Type | Description |
| ----------------- | --------------------------- | --------------------------------------- |
| visible | Ref<boolean> | Open state |
| searchQuery | Ref<string> | Current search query |
| activeIndex | Ref<number> | Currently highlighted item index |
| items | Ref<CommandItemData[]> | Raw item list |
| filteredItems | ComputedRef<...> | Items after filtering |
| groupedItems | ComputedRef<...> | Filtered items grouped by group field |
| canGoBack | ComputedRef<boolean> | Whether there is a parent sub-page |
| pageTitle | ComputedRef<string> | Title of the current sub-page |
| open() | () => void | Open the menu |
| close() | () => void | Close and reset search |
| toggle() | () => void | Toggle open state |
| selectNext() | () => void | Move active index down |
| selectPrev() | () => void | Move active index up |
| selectCurrent() | () => void | Select currently active item |
| pushPage() | (subItems, title) => void | Navigate to a sub-page |
| goBack() | () => void | Return to the parent page |
Bundle Size
| Format | Size | | ------- | ------- | | ESM | 21.2 kB | | UMD | 17.2 kB | | Gzipped | 6.2 kB |
License
MIT © yvng-jie
