Usage
The CommandPalette component leverages Fuse.js to provide robust and efficient fuzzy search functionality.
Groups
When searching for a command, the groups are filtered and the matching commands are presented in order of relevance. Use the groups prop as an array of objects with the following properties:
id: stringlabel?: stringslot?: stringitems?: CommandPaletteItem[]filter?: booleanpostFilter?: (searchTerm: string, items: T[]) => T[]highlightedIcon?: string
Each group takes some items as an array of objects with the following properties:
prefix?: stringlabel?: stringsuffix?: stringicon?: stringavatar?: AvatarPropschip?: ChipPropskbds?: string[] | KbdProps[]disabled?: booleanslot?: stringselect?(e?: Event): void
<script setup lang="ts">
const groups = ref([
{
id: 'suggestions',
label: 'Suggestions',
items: [
{
label: 'Calendar',
icon: 'i-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-map'
}
]
},
{
id: 'actions',
items: [
{
label: 'Add new file',
suffix: 'Create a new file in the current directory or workspace.',
icon: 'i-heroicons-document-plus',
kbds: [
'meta',
'N'
]
},
{
label: 'Add new folder',
suffix: 'Create a new folder in the current directory or workspace.',
icon: 'i-heroicons-folder-plus',
kbds: [
'meta',
'F'
]
},
{
label: 'Add hashtag',
suffix: 'Add a hashtag to the current item.',
icon: 'i-heroicons-hashtag',
kbds: [
'meta',
'H'
]
},
{
label: 'Add label',
suffix: 'Add a label to the current item.',
icon: 'i-heroicons-tag',
kbds: [
'meta',
'L'
]
}
]
}
])
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1 max-h-80" />
</template>
id for each group otherwise the group will be ignored.Placeholder
Use the placeholder prop to change the placeholder text.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-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-heroicons-magnifying-glass-20-solid.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-map'
}
]
}
])
</script>
<template>
<UCommandPalette icon="i-heroicons-cube" :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-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-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-heroicons-arrow-path-20-solid.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-map'
}
]
}
])
</script>
<template>
<UCommandPalette loading loading-icon="i-heroicons-arrow-path-rounded-square" :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-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-map'
}
]
}
])
</script>
<template>
<UCommandPalette disabled :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-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-map'
}
]
}
])
</script>
<template>
<UCommandPalette close :groups="groups" class="flex-1" />
</template>
You can also pass all the props of the Button component to customize it.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-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-heroicons-x-mark-20-solid.
<script setup lang="ts">
const groups = ref([
{
id: 'apps',
items: [
{
label: 'Calendar',
icon: 'i-heroicons-calendar'
},
{
label: 'Music',
icon: 'i-heroicons-musical-note'
},
{
label: 'Maps',
icon: 'i-heroicons-map'
}
]
}
])
</script>
<template>
<UCommandPalette close close-icon="i-heroicons-arrow-right" :groups="groups" class="flex-1" />
</template>
Examples
Control selected item(s)
You can control the selected item by using the default-value prop or the v-model directive.
<script setup lang="ts">
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
const selected = ref(users[0])
function onSelect(item: any) {
console.log(item)
}
</script>
<template>
<UCommandPalette
v-model="selected"
:groups="[{ id: 'users', items: users }]"
class="flex-1"
@update:model-value="onSelect"
/>
</template>
select field on each item and/or the @update:model-value event.Use the multiple prop to allow multiple selections.
<script setup lang="ts">
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
const selected = ref([users[0], users[1]])
function onSelect(items: any) {
console.log(items)
}
</script>
<template>
<UCommandPalette
v-model="selected"
multiple
:groups="[{ id: 'users', items: users }]"
class="flex-1"
@update:model-value="onSelect"
/>
</template>
default-value prop or the v-model directive.Control search term
Use the v-model:search-term directive to control the search term.
<script setup lang="ts">
const users = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
const searchTerm = ref('Th')
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:groups="[{ id: 'users', items: users }]"
class="flex-1"
/>
</template>
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', {
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>
Without internal search
You can set the filter field to false on a group to disable the internal search and use your own search logic.
<script setup lang="ts">
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 || [],
filter: false
}])
</script>
<template>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
class="flex-1 h-80"
/>
</template>
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: '/getting-started#whats-new-in-v3',
label: 'What\'s new in v3?',
level: 2
}, {
id: '/getting-started#radix-vue-3',
label: 'Radix Vue',
level: 3
}, {
id: '/getting-started#tailwind-css-v4',
label: 'Tailwind CSS v4',
level: 3
}, {
id: '/getting-started#tailwind-variants',
label: 'Tailwind Variants',
level: 3
}, {
id: '/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', {
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>
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-heroicons-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' }"
/>
</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', {
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 || [],
filter: false
}])
</script>
<template>
<UModal>
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-heroicons-magnifying-glass"
/>
<template #content>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-96"
/>
</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', {
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 || [],
filter: false
}])
</script>
<template>
<UDrawer>
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-heroicons-magnifying-glass"
/>
<template #content>
<UCommandPalette
v-model:search-term="searchTerm"
:loading="status === 'pending'"
:groups="groups"
placeholder="Search users..."
class="h-96 border-t border-[--ui-border]"
/>
</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 = [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
</script>
<template>
<UModal v-model:open="open">
<UButton
label="Search users..."
color="neutral"
variant="subtle"
icon="i-heroicons-magnifying-glass"
/>
<template #content>
<UCommandPalette
close
:groups="[{ id: 'users', items: users }]"
@update:open="open = $event"
/>
</template>
</UModal>
</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-heroicons-user',
kbds: ['meta', 'P']
}, {
label: 'Billing',
icon: 'i-heroicons-credit-card',
kbds: ['meta', 'B'],
slot: 'billing'
}, {
label: 'Notifications',
icon: 'i-heroicons-bell'
}, {
label: 'Security',
icon: 'i-heroicons-lock-closed'
}]
}, {
id: 'users',
label: 'Users',
slot: 'users',
items: [
{ id: 1, label: 'Durward Reynolds' },
{ id: 2, label: 'Kenton Towne' },
{ id: 3, label: 'Therese Wunsch' },
{ id: 4, label: 'Benedict Kessler' },
{ id: 5, label: 'Katelyn Rohan' }
]
}]
</script>
<template>
<UCommandPalette :groups="groups" class="flex-1 h-80">
<template #users-leading="{ index }">
<UAvatar :src="`https://i.pravatar.cc/120?img=${index}`" size="2xs" />
</template>
<template #billing-label="{ item }">
{{ item.label }}
<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 |
|---|---|---|
searchTerm |
| |
icon |
|
The icon displayed in the input. |
selectedIcon |
|
The icon displayed when an item is selected. |
placeholder |
|
The placeholder text for the input. |
close |
|
Display a close button in the input (useful when inside a Modal for example).
|
closeIcon |
|
The icon displayed in the close button. |
groups |
| |
fuse |
|
Options for useFuse. |
multiple |
Whether multiple options can be selected or not. | |
defaultValue |
The value of the combobox when initially rendered. Use when you do not need to control the state of the Combobox | |
modelValue |
|
The controlled value of the Combobox. Can be binded-with with |
disabled |
When | |
selectedValue |
The current highlighted value of the COmbobox. Can be binded-with | |
resetSearchTermOnBlur |
|
Whether to reset the searchTerm when the Combobox input blurred |
loading |
When | |
loadingIcon |
|
The icon when the |
ui |
|
Slots
| Slot | Type |
|---|---|
empty |
|
close |
|
item |
|
item-leading |
|
item-label |
|
item-trailing |
|
Emits
| Event | Type |
|---|---|
update:modelValue |
|
update:open |
|
update:searchTerm |
|
update:selectedValue |
|
Theme
export default defineAppConfig({
ui: {
commandPalette: {
slots: {
root: 'flex flex-col min-h-0 min-w-0 divide-y divide-[--ui-border]',
input: '[&>input]:h-12',
close: '',
content: 'relative overflow-hidden',
viewport: 'divide-y divide-[--ui-border] scroll-py-1',
group: 'p-1 isolate',
empty: 'py-6 text-center text-sm',
label: 'px-2 py-1.5 text-xs font-semibold text-[--ui-text-highlighted]',
item: [
'group relative w-full flex items-center gap-2 px-2 py-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 text-[--ui-text] data-highlighted:text-[--ui-text-highlighted] data-highlighted:before:bg-[--ui-bg-elevated]/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'shrink-0 size-5 text-[--ui-text-dimmed] group-data-highlighted:text-[--ui-text]',
'transition-colors'
],
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-[--ui-text-dimmed] hidden group-data-highlighted:inline-flex',
itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0 gap-0.5',
itemTrailingKbdsSize: 'md',
itemLabel: 'truncate space-x-1',
itemLabelBase: 'text-[--ui-text-highlighted] [&>mark]:text-[--ui-bg] [&>mark]:bg-[--ui-primary]',
itemLabelPrefix: 'text-[--ui-text]',
itemLabelSuffix: 'text-[--ui-text-dimmed] [&>mark]:text-[--ui-bg] [&>mark]:bg-[--ui-primary]'
}
}
}
})