Usage
Use the v-model directive to control the value of the CommandPalette or the default-value prop to set the initial value when you do not need to control its state.
<script setup lang="ts">
const groups = ref([
{
id: 'users',
label: 'Users',
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
}
])
const value = ref({})
</script>
<template>
<UCommandPalette v-model="value" :groups="groups" class="flex-1 h-80" />
</template>
Groups
The CommandPalette component filters groups and ranks matching commands by relevance as users type. It provides dynamic, instant search results for efficient command discovery. Use the groups prop as an array of objects with the following properties:
id: stringlabel?: stringslot?: stringitems?: CommandPaletteItem[]ignoreFilter?: booleanpostFilter?: (searchTerm: string, items: T[]) => T[]highlightedIcon?: string
id for each group otherwise the group will be ignored.Each group contains an items array of objects that define the commands. Each item can have the following properties:
prefix?: stringlabel?: stringsuffix?: stringicon?: stringavatar?: AvatarPropschip?: ChipPropskbds?: string[] | KbdProps[]active?: booleanloading?: booleandisabled?: booleanslot?: stringplaceholder?: stringchildren?: CommandPaletteItem[]onSelect?: (e: Event) => voidclass?: anyui?: { item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemLabelPrefix?: ClassNameValue, itemLabelBase?: ClassNameValue, itemLabelSuffix?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingKbds?: ClassNameValue, itemTrailingKbdsSize?: ClassNameValue, itemTrailingHighlightedIcon?: ClassNameValue, itemTrailingIcon?: ClassNameValue }
You can pass any property from the Link component such as to, target, etc.
<script setup lang="ts">
const groups = ref([
{
id: 'users',
label: 'Users',
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
}
])
const value = ref({})
</script>
<template>
<UCommandPalette v-model="value" :groups="groups" class="flex-1" />
</template>
Multiple
Use the multiple prop to allow multiple selections.
<script setup lang="ts">
const groups = ref([
{
id: 'users',
label: 'Users',
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
}
])
const value = ref([])
</script>
<template>
<UCommandPalette multiple v-model="value" :groups="groups" class="flex-1" />
</template>
default-value prop or the v-model directive.Placeholder
Use the placeholder prop to change the placeholder text.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette placeholder="Search an app..." :groups="groups" class="flex-1" />
</template>
Icon
Use the icon prop to customize the input Icon. Defaults to i-lucide-search.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette icon="i-lucide-box" :groups="groups" class="flex-1" />
</template>
Selected Icon
Use the selected-icon prop to customize the selected item Icon. Defaults to i-lucide-check.
<script setup lang="ts">
const groups = ref([
{
id: 'users',
label: 'Users',
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
}
])
const value = ref([
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
}
])
</script>
<template>
<UCommandPalette multiple v-model="value" selected-icon="i-lucide-circle-check" :groups="groups" class="flex-1" />
</template>
Trailing Icon
Use the trailing-icon prop to customize the trailing Icon when an item has children. Defaults to i-lucide-chevron-right.
<script setup lang="ts">
const groups = ref([
{
id: 'actions',
items: [
{
label: 'Share',
icon: 'i-lucide-share',
children: [
{
label: 'Email',
icon: 'i-lucide-mail'
},
{
label: 'Copy',
icon: 'i-lucide-copy'
},
{
label: 'Link',
icon: 'i-lucide-link'
}
]
}
]
}
])
</script>
<template>
<UCommandPalette trailing-icon="i-lucide-arrow-right" :groups="groups" class="flex-1" />
</template>
Loading
Use the loading prop to show a loading icon on the CommandPalette.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette loading :groups="groups" class="flex-1" />
</template>
Loading Icon
Use the loading-icon prop to customize the loading icon. Defaults to i-lucide-loader-circle.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette loading loading-icon="i-lucide-loader" :groups="groups" class="flex-1" />
</template>
Close
Use the close prop to display a Button to dismiss the CommandPalette.
update:open event will be emitted when the close button is clicked.<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette close :groups="groups" class="flex-1" />
</template>
You can pass any property from the Button component to customize it.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette
:close="{
color: 'primary',
variant: 'outline',
class: 'rounded-full'
}"
:groups="groups"
class="flex-1"
/>
</template>
Close Icon
Use the close-icon prop to customize the close button Icon. Defaults to i-lucide-x.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette close close-icon="i-lucide-arrow-right" :groups="groups" class="flex-1" />
</template>
Back
Use the back prop to customize or hide the back button (with false value) displayed when navigating into a submenu.
You can pass any property from the Button component to customize it.
<script setup lang="ts">
const groups = ref([
{
id: 'actions',
items: [
{
label: 'Share',
icon: 'i-lucide-share',
children: [
{
label: 'Email',
icon: 'i-lucide-mail'
},
{
label: 'Copy',
icon: 'i-lucide-copy'
},
{
label: 'Link',
icon: 'i-lucide-link'
}
]
}
]
}
])
</script>
<template>
<UCommandPalette
:back="{
color: 'primary'
}"
:groups="groups"
class="flex-1"
/>
</template>
Back Icon
Use the back-icon prop to customize the back button Icon. Defaults to i-lucide-arrow-left.
<script setup lang="ts">
const groups = ref([
{
id: 'actions',
items: [
{
label: 'Share',
icon: 'i-lucide-share',
children: [
{
label: 'Email',
icon: 'i-lucide-mail'
},
{
label: 'Copy',
icon: 'i-lucide-copy'
},
{
label: 'Link',
icon: 'i-lucide-link'
}
]
}
]
}
])
</script>
<template>
<UCommandPalette back-icon="i-lucide-house" :groups="groups" class="flex-1" />
</template>
Disabled
Use the disabled prop to disable the CommandPalette.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-lucide-calendar'
},
{
label: 'Music',
icon: 'i-lucide-music'
},
{
label: 'Maps',
icon: 'i-lucide-map'
}
]
}
])
</script>
<template>
<UCommandPalette disabled :groups="groups" class="flex-1" />
</template>
Examples
Control selected item(s)
You can control the selected item(s) by using the default-value prop or the v-model directive, by using the onSelect field on each item or by using the @update:model-value event.
<script setup lang="ts">
const toast = useToast()
const groups = ref([
{
id: 'users',
label: 'Users',
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
to: 'https://github.com/HugoRCD',
target: '_blank',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
to: 'https://github.com/sandros94',
target: '_blank',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
to: 'https://github.com/J-Michalek',
target: '_blank',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
to: 'https://github.com/genu',
target: '_blank',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
},
{
id: 'actions',
items: [
{
label: 'Add new file',
suffix: 'Create a new file in the current directory or workspace.',
icon: 'i-lucide-file-plus',
kbds: [
'meta',
'N'
],
onSelect() {
toast.add({ title: 'Add new file' })
}
},
{
label: 'Add new folder',
suffix: 'Create a new folder in the current directory or workspace.',
icon: 'i-lucide-folder-plus',
kbds: [
'meta',
'F'
],
onSelect() {
toast.add({ title: 'Add new folder' })
}
},
{
label: 'Add hashtag',
suffix: 'Add a hashtag to the current item.',
icon: 'i-lucide-hash',
kbds: [
'meta',
'H'
],
onSelect() {
toast.add({ title: 'Add hashtag' })
}
},
{
label: 'Add label',
suffix: 'Add a label to the current item.',
icon: 'i-lucide-tag',
kbds: [
'meta',
'L'
],
onSelect() {
toast.add({ title: 'Add label' })
}
}
]
}
])
function onSelect(item: any) {
console.log(item)
}
</script>
<template>
<UCommandPalette
:groups="groups"
class="flex-1 h-80"
@update:model-value="onSelect"
/>
</template>
Control search term
Use the v-model:search-term directive to control the search term.
<script setup lang="ts">
const users = [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
to: 'https://github.com/HugoRCD',
target: '_blank',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
to: 'https://github.com/sandros94',
target: '_blank',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
to: 'https://github.com/J-Michalek',
target: '_blank',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
to: 'https://github.com/genu',
target: '_blank',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
const searchTerm = ref('B')
function onSelect() {
searchTerm.value = ''
}
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:groups="[{ id: 'users', items: users }]"
class="flex-1"
@update:model-value="onSelect"
/>
</template>
@update:model-value event to reset the search term when an item is selected.With children in items
You can create hierarchical menus by using the children property in items. When an item has children, it will automatically display a chevron icon and enable navigation into a submenu.
<script setup lang="ts">
const toast = useToast()
const groups = [
{
id: 'actions',
label: 'Actions',
items: [
{
label: 'Create new',
icon: 'i-lucide-plus',
children: [
{
label: 'New file',
icon: 'i-lucide-file-plus',
suffix: 'Create a new file in the current directory',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New file created!' })
},
kbds: ['meta', 'N']
},
{
label: 'New folder',
icon: 'i-lucide-folder-plus',
suffix: 'Create a new folder in the current directory',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New folder created!' })
},
kbds: ['meta', 'F']
},
{
label: 'New project',
icon: 'i-lucide-folder-git',
suffix: 'Create a new project from a template',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'New project created!' })
},
kbds: ['meta', 'P']
}
]
},
{
label: 'Share',
icon: 'i-lucide-share',
children: [
{
label: 'Copy link',
icon: 'i-lucide-link',
suffix: 'Copy a link to the current item',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Link copied to clipboard!' })
},
kbds: ['meta', 'L']
},
{
label: 'Share via email',
icon: 'i-lucide-mail',
suffix: 'Share the current item via email',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Share via email dialog opened!' })
}
},
{
label: 'Share on social',
icon: 'i-lucide-share-2',
suffix: 'Share the current item on social media',
children: [
{
label: 'Twitter',
icon: 'i-simple-icons-twitter',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Shared on Twitter!' })
}
},
{
label: 'LinkedIn',
icon: 'i-simple-icons-linkedin',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Shared on LinkedIn!' })
}
},
{
label: 'Facebook',
icon: 'i-simple-icons-facebook',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Shared on Facebook!' })
}
}
]
}
]
},
{
label: 'Settings',
icon: 'i-lucide-settings',
children: [
{
label: 'General',
icon: 'i-lucide-sliders',
suffix: 'Configure general settings',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'General settings opened!' })
}
},
{
label: 'Appearance',
icon: 'i-lucide-palette',
suffix: 'Customize the appearance',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Appearance settings opened!' })
}
},
{
label: 'Security',
icon: 'i-lucide-shield',
suffix: 'Manage security settings',
onSelect(e: Event) {
e.preventDefault()
toast.add({ title: 'Security settings opened!' })
}
}
]
}
]
}
]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1" />
</template>
- The search term is reset
- A back button appears in the input
- You can go back to the previous group by pressing the ⌫ key
With fetched items
You can fetch items from an API and use them in the CommandPalette.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'command-palette-users',
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || []
}])
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
class="flex-1 h-80"
/>
</template>
With ignore filter
You can set the ignoreFilter field to true on a group to disable the internal search and use your own search logic.
<script setup lang="ts">
import { refDebounced } from '@vueuse/core'
const searchTerm = ref('')
const searchTermDebounced = refDebounced(searchTerm, 200)
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
params: { q: searchTermDebounced },
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
class="flex-1 h-80"
/>
</template>
refDebounced to debounce the API calls.With post-filtered items
You can use the postFilter field on a group to filter items after the search happened.
<script setup lang="ts">
const items = [
{
id: '/',
label: 'Introduction',
level: 1
},
{
id: '/docs/getting-started#whats-new-in-v3',
label: 'What\'s new in v3?',
level: 2
},
{
id: '/docs/getting-started#reka-ui',
label: 'Reka UI',
level: 3
},
{
id: '/docs/getting-started#tailwind-css-v4',
label: 'Tailwind CSS v4',
level: 3
},
{
id: '/docs/getting-started#tailwind-variants',
label: 'Tailwind Variants',
level: 3
},
{
id: '/docs/getting-started/installation',
label: 'Installation',
level: 1
}
]
function postFilter(searchTerm: string, items: any[]) {
// Filter only first level items if no searchTerm
if (!searchTerm) {
return items?.filter(item => item.level === 1)
}
return items
}
</script>
<template>
<UCommandPalette :groups="[{ id: 'files', items, postFilter }]" class="flex-1" />
</template>
With custom fuse search
You can use the fuse prop to override the options of useFuse which defaults to:
{
fuseOptions: {
ignoreLocation: true,
threshold: 0.1,
keys: ['label', 'suffix']
},
resultLimit: 12,
matchAllWhenSearchEmpty: true
}
fuseOptions are the options of Fuse.js, the resultLimit is the maximum number of results to return and the matchAllWhenSearchEmpty is a boolean to match all items when the search term is empty.You can for example set { fuseOptions: { includeMatches: true } } to highlight the search term in the items.
<script setup lang="ts">
const { data: users } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'command-palette-users',
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
</script>
<template>
<UCommandPalette
:groups="[{ id: 'users', items: users || [] }]"
:fuse="{ fuseOptions: { includeMatches: true } }"
class="flex-1 h-80"
/>
</template>
With virtualization Soon
Use the virtualize prop to enable virtualization for large lists as a boolean or an object with options like { estimateSize: 32, overscan: 12 }.
<script setup lang="ts">
import type { CommandPaletteItem } from '@nuxt/ui'
const items: CommandPaletteItem[] = Array(1000)
.fill(0)
.map((_, value) => ({
label: `item-${value}`,
value
}))
const groups = [
{
id: 'items',
items
}
]
</script>
<template>
<UCommandPalette
virtualize
:fuse="{ resultLimit: 1000 }"
:groups="groups"
class="flex-1 h-80"
/>
</template>
Within a Popover
You can use the CommandPalette component inside a Popover's content.
<script setup lang="ts">
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info' as const
}
}
])
const label = ref([])
</script>
<template>
<UPopover :content="{ side: 'right', align: 'start' }">
<UButton
icon="i-lucide-tag"
label="Select labels"
color="neutral"
variant="subtle"
/>
<template #content>
<UCommandPalette
v-model="label"
multiple
placeholder="Search labels..."
:groups="[{ id: 'labels', items }]"
:ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }"
/>
</template>
</UPopover>
</template>
Within a Modal
You can use the CommandPalette component inside a Modal's content.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'command-palette-users',
params: { q: searchTerm },
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
</script>
<template>
<UModal>
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-lucide-search"
/>
<template #content>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-80"
/>
</template>
</UModal>
</template>
Within a Drawer
You can use the CommandPalette component inside a Drawer's content.
<script setup lang="ts">
const searchTerm = ref('')
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
key: 'command-palette-users',
params: { q: searchTerm },
transform: (data: { id: number, name: string, email: string }[]) => {
return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` } })) || []
},
lazy: true
})
const groups = computed(() => [{
id: 'users',
label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
items: users.value || [],
ignoreFilter: true
}])
</script>
<template>
<UDrawer :handle="false">
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-lucide-search"
/>
<template #content>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-80"
/>
</template>
</UDrawer>
</template>
Listen open state
When using the close prop, you can listen to the update:open event when the button is clicked.
<script setup lang="ts">
const open = ref(false)
const users = [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank',
avatar: {
src: 'https://github.com/benjamincanac.png'
}
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank',
avatar: {
src: 'https://github.com/romhml.png'
}
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank',
avatar: {
src: 'https://github.com/atinux.png'
}
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
to: 'https://github.com/HugoRCD',
target: '_blank',
avatar: {
src: 'https://github.com/HugoRCD.png'
}
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
to: 'https://github.com/sandros94',
target: '_blank',
avatar: {
src: 'https://github.com/sandros94.png'
}
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank',
avatar: {
src: 'https://github.com/danielroe.png'
}
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
to: 'https://github.com/J-Michalek',
target: '_blank',
avatar: {
src: 'https://github.com/J-Michalek.png'
}
},
{
label: 'Eugen Istoc',
suffix: 'genu',
to: 'https://github.com/genu',
target: '_blank',
avatar: {
src: 'https://github.com/genu.png'
}
}
]
</script>
<template>
<UModal v-model:open="open">
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-lucide-search"
/>
<template #content>
<UCommandPalette close :groups="[{ id: 'users', items: users }]" @update:open="open = $event" />
</template>
</UModal>
</template>
Modal for example.With footer slot
Use the #footer slot to add custom content at the bottom of the CommandPalette, such as keyboard shortcuts help or additional actions.
<script setup lang="ts">
const groups = [
{
id: 'actions',
items: [
{
label: 'Add new file',
suffix: 'Create a new file in the current directory',
icon: 'i-lucide-file-plus',
kbds: ['meta', 'N']
},
{
label: 'Add new folder',
suffix: 'Create a new folder in the current directory',
icon: 'i-lucide-folder-plus',
kbds: ['meta', 'F']
},
{
label: 'Search files',
suffix: 'Search across all files in the project',
icon: 'i-lucide-search',
kbds: ['meta', 'P']
},
{
label: 'Settings',
suffix: 'Open application settings',
icon: 'i-lucide-settings',
kbds: ['meta', ',']
}
]
},
{
id: 'recent',
label: 'Recent',
items: [
{
label: 'project.vue',
suffix: 'components/',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'readme.md',
suffix: 'docs/',
icon: 'i-vscode-icons-file-type-markdown'
},
{
label: 'package.json',
suffix: 'root/',
icon: 'i-vscode-icons-file-type-node'
}
]
}
]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1 h-80">
<template #footer>
<div class="flex items-center justify-between gap-2">
<UIcon name="i-simple-icons-nuxtdotjs" class="size-5 text-dimmed ml-1" />
<div class="flex items-center gap-1">
<UButton color="neutral" variant="ghost" label="Open Command" class="text-dimmed" size="xs">
<template #trailing>
<UKbd value="enter" />
</template>
</UButton>
<USeparator orientation="vertical" class="h-4" />
<UButton color="neutral" variant="ghost" label="Actions" class="text-dimmed" size="xs">
<template #trailing>
<UKbd value="meta" />
<UKbd value="k" />
</template>
</UButton>
</div>
</div>
</template>
</UCommandPalette>
</template>
With custom slot
Use the slot property to customize a specific item or group.
You will have access to the following slots:
#{{ item.slot }}#{{ item.slot }}-leading#{{ item.slot }}-label#{{ item.slot }}-trailing#{{ group.slot }}#{{ group.slot }}-leading#{{ group.slot }}-label#{{ group.slot }}-trailing
<script setup lang="ts">
const groups = [
{
id: 'settings',
items: [
{
label: 'Profile',
icon: 'i-lucide-user',
kbds: ['meta', 'P']
},
{
label: 'Billing',
icon: 'i-lucide-credit-card',
kbds: ['meta', 'B'],
slot: 'billing' as const
},
{
label: 'Notifications',
icon: 'i-lucide-bell'
},
{
label: 'Security',
icon: 'i-lucide-lock'
}
]
},
{
id: 'users',
label: 'Users',
slot: 'users' as const,
items: [
{
label: 'Benjamin Canac',
suffix: 'benjamincanac',
to: 'https://github.com/benjamincanac',
target: '_blank'
},
{
label: 'Romain Hamel',
suffix: 'romhml',
to: 'https://github.com/romhml',
target: '_blank'
},
{
label: 'Sébastien Chopin',
suffix: 'atinux',
to: 'https://github.com/atinux',
target: '_blank'
},
{
label: 'Hugo Richard',
suffix: 'HugoRCD',
to: 'https://github.com/HugoRCD',
target: '_blank'
},
{
label: 'Sandro Circi',
suffix: 'sandros94',
to: 'https://github.com/sandros94',
target: '_blank'
},
{
label: 'Daniel Roe',
suffix: 'danielroe',
to: 'https://github.com/danielroe',
target: '_blank'
},
{
label: 'Jakub Michálek',
suffix: 'J-Michalek',
to: 'https://github.com/J-Michalek',
target: '_blank'
},
{
label: 'Eugen Istoc',
suffix: 'genu',
to: 'https://github.com/genu',
target: '_blank'
}
]
}
]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1 h-80">
<template #users-leading="{ item }">
<UAvatar :src="`https://github.com/${item.suffix}.png`" size="2xs" />
</template>
<template #billing-label="{ item }">
<span class="font-medium text-primary">{{ item.label }}</span>
<UBadge variant="subtle" size="sm">
50% off
</UBadge>
</template>
</UCommandPalette>
</template>
#item, #item-leading, #item-label and #item-trailing slots to customize all items.API
Props
| Prop | Default | Type |
|---|---|---|
as |
|
The element or component this component should render as. |
icon |
|
The icon displayed in the input. |
trailingIcon |
|
The icon displayed on the right side of the input. |
selectedIcon |
|
The icon displayed when an item is selected. |
childrenIcon |
|
The icon displayed when an item has children. |
placeholder |
|
The placeholder text for the input. |
autofocus |
|
Automatically focus the input when component is mounted. |
close |
|
Display a close button in the input (useful when inside a Modal for example).
|
closeIcon |
|
The icon displayed in the close button. |
back |
|
Display a button to navigate back in history.
|
backIcon |
|
The icon displayed in the back button. |
groups |
| |
fuse |
|
Options for useFuse. |
virtualize |
|
Enable virtualization for large lists. Note: when enabled, all groups are flattened into a single list due to a limitation of Reka UI (https://github.com/unovue/reka-ui/issues/1885).
|
labelKey |
|
The key used to get the label from the item. |
descriptionKey |
|
The key used to get the description from the item. |
multiple |
Whether multiple options can be selected or not. | |
disabled |
When | |
modelValue |
|
The controlled value of the listbox. Can be binded with with |
defaultValue |
The value of the listbox when initially rendered. Use when you do not need to control the state of the Listbox | |
highlightOnHover |
When | |
selectionBehavior |
|
How multiple selection should behave in the collection. |
loading |
When | |
loadingIcon |
|
The icon when the |
searchTerm |
|
|
ui |
|
Slots
| Slot | Type |
|---|---|
empty |
|
footer |
|
back |
|
close |
|
item |
|
item-leading |
|
item-label |
|
item-description |
|
item-trailing |
|
Emits
| Event | Type |
|---|---|
update:modelValue |
|
highlight |
|
entryFocus |
|
leave |
|
update:open |
|
update:searchTerm |
|
Theme
export default defineAppConfig({
ui: {
commandPalette: {
slots: {
root: 'flex flex-col min-h-0 min-w-0 divide-y divide-default',
input: '[&>input]:h-12',
close: '',
back: 'p-0',
content: 'relative overflow-hidden flex flex-col',
footer: 'p-1',
viewport: 'relative scroll-py-1 overflow-y-auto flex-1 focus:outline-none',
group: 'p-1 isolate',
empty: 'py-6 text-center text-sm text-muted',
label: 'p-1.5 text-xs font-semibold text-highlighted',
item: 'group relative w-full flex items-start gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75',
itemLeadingIcon: 'shrink-0 size-5',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '2xs',
itemLeadingChip: 'shrink-0 size-5',
itemLeadingChipSize: 'md',
itemTrailing: 'ms-auto inline-flex gap-1.5 items-center',
itemTrailingIcon: 'shrink-0 size-5',
itemTrailingHighlightedIcon: 'shrink-0 size-5 text-dimmed hidden group-data-highlighted:inline-flex',
itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5',
itemTrailingKbdsSize: 'md',
itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
itemLabel: 'truncate space-x-1 text-dimmed',
itemDescription: 'truncate text-muted',
itemLabelBase: 'text-highlighted [&>mark]:text-inverted [&>mark]:bg-primary',
itemLabelPrefix: 'text-default',
itemLabelSuffix: 'text-dimmed [&>mark]:text-inverted [&>mark]:bg-primary'
},
variants: {
virtualize: {
true: {
viewport: 'p-1 isolate'
},
false: {
viewport: 'divide-y divide-default'
}
},
active: {
true: {
item: 'text-highlighted before:bg-elevated',
itemLeadingIcon: 'text-default'
},
false: {
item: [
'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
'transition-colors'
]
}
},
loading: {
true: {
itemLeadingIcon: 'animate-spin'
}
}
}
}
}
})
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
commandPalette: {
slots: {
root: 'flex flex-col min-h-0 min-w-0 divide-y divide-default',
input: '[&>input]:h-12',
close: '',
back: 'p-0',
content: 'relative overflow-hidden flex flex-col',
footer: 'p-1',
viewport: 'relative scroll-py-1 overflow-y-auto flex-1 focus:outline-none',
group: 'p-1 isolate',
empty: 'py-6 text-center text-sm text-muted',
label: 'p-1.5 text-xs font-semibold text-highlighted',
item: 'group relative w-full flex items-start gap-1.5 p-1.5 text-sm select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75',
itemLeadingIcon: 'shrink-0 size-5',
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '2xs',
itemLeadingChip: 'shrink-0 size-5',
itemLeadingChipSize: 'md',
itemTrailing: 'ms-auto inline-flex gap-1.5 items-center',
itemTrailingIcon: 'shrink-0 size-5',
itemTrailingHighlightedIcon: 'shrink-0 size-5 text-dimmed hidden group-data-highlighted:inline-flex',
itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5',
itemTrailingKbdsSize: 'md',
itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
itemLabel: 'truncate space-x-1 text-dimmed',
itemDescription: 'truncate text-muted',
itemLabelBase: 'text-highlighted [&>mark]:text-inverted [&>mark]:bg-primary',
itemLabelPrefix: 'text-default',
itemLabelSuffix: 'text-dimmed [&>mark]:text-inverted [&>mark]:bg-primary'
},
variants: {
virtualize: {
true: {
viewport: 'p-1 isolate'
},
false: {
viewport: 'divide-y divide-default'
}
},
active: {
true: {
item: 'text-highlighted before:bg-elevated',
itemLeadingIcon: 'text-default'
},
false: {
item: [
'text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
'transition-colors'
]
}
},
loading: {
true: {
itemLeadingIcon: 'animate-spin'
}
}
}
}
}
})
]
})
Changelog
5cb65 — feat: import @nuxt/ui-pro components
2ba94 — fix: add presentation role to viewport
e6e51 — fix: class should have priority over ui prop
d2526 — fix: consistent alignement with other components
bc61d — fix: use group.id as key
06dfb — chore: improve placeholder tsdoc
ef861 — chore: add eol in script tag to fix syntax highlight