Usage
Use a v-model
to display a searchable and selectable list of commands.
<script setup>
const people = [
{ id: 1, label: 'Wade Cooper' },
{ id: 2, label: 'Arlene Mccoy' },
{ id: 3, label: 'Devon Webb' },
{ id: 4, label: 'Tom Cook' },
{ id: 5, label: 'Tanya Fox' },
{ id: 6, label: 'Hellen Schmidt' },
{ id: 7, label: 'Caroline Schultz' },
{ id: 8, label: 'Mason Heaney' },
{ id: 9, label: 'Claudie Smitham' },
{ id: 10, label: 'Emil Schaefer' }
]
const selected = ref([people[3]])
</script>
<template>
<UCommandPalette
v-model="selected"
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
:fuse="{ resultLimit: 6, fuseOptions: { threshold: 0.1 } }"
/>
</template>
You can put a CommandPalette
anywhere you want but it's most commonly used inside of a modal.
<script setup>
const isOpen = ref(false)
const people = [
{ id: 1, label: 'Wade Cooper' },
{ id: 2, label: 'Arlene Mccoy' },
{ id: 3, label: 'Devon Webb' },
{ id: 4, label: 'Tom Cook' },
{ id: 5, label: 'Tanya Fox' },
{ id: 6, label: 'Hellen Schmidt' },
{ id: 7, label: 'Caroline Schultz' },
{ id: 8, label: 'Mason Heaney' },
{ id: 9, label: 'Claudie Smitham' },
{ id: 10, label: 'Emil Schaefer' }
]
const selected = ref([])
</script>
<template>
<div>
<UButton label="Open" @click="isOpen = true" />
<UModal v-model="isOpen">
<UCommandPalette
v-model="selected"
multiple
nullable
:groups="[{ key: 'people', commands: people }]"
/>
</UModal>
</div>
</template>
You can pass multiple groups of commands to the component. Each group will be separated by a divider and will display a label.
Without a v-model
, you can also listen on @update:model-value
to navigate to a link or do something else when a command is clicked.
Recent searches
<script setup>
const router = useRouter()
const toast = useToast()
const commandPaletteRef = ref()
const users = [
{ id: 'benjamincanac', label: 'benjamincanac', href: 'https://github.com/benjamincanac', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' } },
{ id: 'Atinux', label: 'Atinux', href: 'https://github.com/Atinux', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' } },
{ id: 'smarroufin', label: 'smarroufin', href: 'https://github.com/smarroufin', target: '_blank', avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' } }
]
const actions = [
{ id: 'new-file', label: 'Add new file', icon: 'i-heroicons-document-plus', click: () => toast.add({ title: 'New file added!' }), shortcuts: ['⌘', 'N'] },
{ id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => toast.add({ title: 'New folder added!' }), shortcuts: ['⌘', 'F'] },
{ id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => toast.add({ title: 'Hashtag added!' }), shortcuts: ['⌘', 'H'] },
{ id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => toast.add({ title: 'Label added!' }), shortcuts: ['⌘', 'L'] }
]
const groups = computed(() =>
[commandPaletteRef.value?.query ? {
key: 'users',
commands: users
} : {
key: 'recent',
label: 'Recent searches',
commands: users.slice(0, 1)
}, {
key: 'actions',
commands: actions
}].filter(Boolean))
function onSelect (option) {
if (option.click) {
option.click()
} else if (option.to) {
router.push(option.to)
} else if (option.href) {
window.open(option.href, '_blank')
}
}
</script>
<template>
<UCommandPalette ref="commandPaletteRef" :groups="groups" @update:model-value="onSelect" />
</template>
Icon
Use any icon from Iconify by setting the icon
prop by using this pattern: i-{collection_name}-{icon_name}
.
Use the selected-icon
prop to set a different icon or change it globally in ui.commandPalette.default.selectedIcon
. Defaults to i-heroicons-check-20-solid
.
We couldn't find any items.
<UCommandPalette icon="i-heroicons-command-line" />
Loading
Use the loading
prop to show a loading icon.
Use the loading-icon
prop to set a different icon or change it globally in ui.commandPalette.default.loadingIcon
. Defaults to i-heroicons-arrow-path-20-solid
.
We couldn't find any items.
<UCommandPalette loading />
Placeholder
Use the placeholder
prop to change the input placeholder
We couldn't find any items.
<UCommandPalette placeholder="Type a command..." />
Close
Use the close-button
prop to display a close button on the right side of the input.
You can pass all the props of the Button component to customize it through the close-button
prop or globally through ui.commandPalette.default.closeButton
.
We couldn't find any items.
<UCommandPalette
:close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }"
/>
Empty
An empty state will be displayed when there are no results.
Use the empty-state
prop to customize the icon
and label
or change them globally in ui.commandPalette.default.emptyState
.
You can also set it to null
to hide the empty state.
We couldn't find any items.
<UCommandPalette
:empty-state="{ icon: 'i-heroicons-magnifying-glass-20-solid', label: 'We couldn't find any items.', queryLabel: 'We couldn't find any items with that term. Please try again.' }"
placeholder="Type something to see the empty label change"
/>
Full-text search
The CommandPalette component takes care of the full-text search for you with Fuse.js. You can pass all the options of Fuse.js through the fuse
prop.
When searching for a command, the component will look for a label
property on the command by default. You can customize this behavior by overriding the command-attribute
prop. This will also affect the display of the command.
You can also highlight the matches in the command by setting the fuse.fuseOptions.includeMatches
to true
. The CommandPalette component automatically takes care of the highlighting for you.
<template>
<UCommandPalette
command-attribute="title"
:fuse="{
fuseOptions: {
ignoreLocation: true,
includeMatches: true,
threshold: 0,
keys: ['title', 'description', 'children.children.value', 'children.children.children.value']
},
resultLimit: 10
}"
/>
</template>
Async search
You can also pass an async
function to the search
property of a group to perform an async search. The function will receive the query as its first argument and should return an array of commands.
We couldn't find any items.
<script setup>
const groups = computed(() => {
return [{
key: 'users',
label: q => q && `Users matching “${q}”...`,
search: async (q) => {
if (!q) {
return []
}
const users = await $fetch(`https://jsonplaceholder.typicode.com/users`, { params: { q } })
return users.map(user => ({ id: user.id, label: user.name, suffix: user.email }))
}
}].filter(Boolean)
})
</script>
<template>
<UCommandPalette :groups="groups" />
</template>
loading
state will automatically be enabled when a search
function is loading. You can disable this behavior by setting the loading-icon
prop to null
or globally in ui.commandPalette.default.loadingIcon
.Slots
<group>-icon
Use the #<group>-icon
slot to override the left command content which display by default the icon
, avatar
and chip
.
<group>-command
Use the #<group>-command
slot to override the command content which display by default the prefix
, suffix
and label
(customizable through the command-attribute
prop).
<group>-active
Use the #<group>-active
slot to override the right command content (when hovered) which display by default the active
field of the group if provided.
<group>-inactive
Use the #<group>-inactive
slot to override the right command content (when not hovered) which display by default the inactive
field of the group if provided or the shortcuts
of the command.
group
, command
, active
and selected
properties in the slot scope.empty-state
Use the #empty-state
slot to customize the empty state.
<script setup>
const groups = [...]
</script>
<template>
<UCommandPalette :groups="groups">
<template #empty-state>
<div class="flex flex-col items-center justify-center py-6 gap-3">
<span class="italic text-sm">Nothing here!</span>
<UButton label="Add item" />
</div>
</template>
</UCommandPalette>
</template>
Props
config.default.icon
undefined
null
"Search..."
config.default.loadingIcon
"id"
config.default.selectedIcon
200
config.default.emptyState
"label"
"label"
[]
config.default.closeButton as Button
{}
false
false
false
true
true
true
Preset
{
"wrapper": "flex flex-col flex-1 min-h-0 divide-y divide-gray-100 dark:divide-gray-800",
"container": "relative flex-1 overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800 scroll-py-2",
"input": {
"wrapper": "relative flex items-center",
"base": "w-full placeholder-gray-400 dark:placeholder-gray-500 bg-transparent border-0 text-gray-900 dark:text-white focus:ring-0 focus:outline-none",
"padding": "px-4",
"height": "h-12",
"size": "sm:text-sm",
"icon": {
"base": "pointer-events-none absolute start-4 text-gray-400 dark:text-gray-500",
"size": "h-4 w-4",
"padding": "ps-10"
},
"closeButton": "absolute end-4"
},
"emptyState": {
"wrapper": "flex flex-col items-center justify-center flex-1 px-6 py-14 sm:px-14",
"label": "text-sm text-center text-gray-900 dark:text-white",
"queryLabel": "text-sm text-center text-gray-900 dark:text-white",
"icon": "w-6 h-6 mx-auto text-gray-400 dark:text-gray-500 mb-4"
},
"group": {
"wrapper": "p-2",
"label": "px-2 my-2 text-xs font-semibold text-gray-900 dark:text-white",
"container": "text-sm text-gray-700 dark:text-gray-200",
"command": {
"base": "flex justify-between select-none items-center rounded-md px-2 py-1.5 gap-2 relative",
"active": "bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-white",
"inactive": "",
"label": "flex items-center gap-1.5 min-w-0",
"prefix": "text-gray-400 dark:text-gray-500",
"suffix": "text-gray-400 dark:text-gray-500",
"container": "flex items-center gap-2 min-w-0",
"icon": {
"base": "flex-shrink-0 w-4 h-4",
"active": "text-gray-900 dark:text-white",
"inactive": "text-gray-400 dark:text-gray-500"
},
"selectedIcon": {
"base": "h-4 w-4 text-gray-900 dark:text-white flex-shrink-0"
},
"avatar": {
"base": "flex-shrink-0",
"size": "3xs"
},
"chip": {
"base": "flex-shrink-0 w-2 h-2 mx-1 rounded-full"
},
"disabled": "opacity-50",
"shortcuts": "hidden md:inline-flex flex-shrink-0 gap-0.5"
},
"active": "flex-shrink-0 text-gray-500 dark:text-gray-400",
"inactive": "flex-shrink-0 text-gray-500 dark:text-gray-400"
},
"default": {
"icon": "i-heroicons-magnifying-glass-20-solid",
"loadingIcon": "i-heroicons-arrow-path-20-solid",
"emptyState": {
"icon": "i-heroicons-magnifying-glass-20-solid",
"label": "We couldn't find any items.",
"queryLabel": "We couldn't find any items with that term. Please try again."
},
"closeButton": null,
"selectedIcon": "i-heroicons-check-20-solid"
}
}