navigation

Command Palette

Add a customizable command palette to your app.

Usage

Use a v-model to display a searchable and selectable list of commands.

Wade Cooper
Arlene Mccoy
Devon Webb
Tom Cook
Tanya Fox
Hellen Schmidt
<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

benjamincanac
Add new file
Add new folder
Add hashtag
Add label
<script setup>
const router = useRouter()

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: () => alert('New file'), shortcuts: ['', 'N'] },
  { id: 'new-folder', label: 'Add new folder', icon: 'i-heroicons-folder-plus', click: () => alert('New folder'), shortcuts: ['', 'F'] },
  { id: 'hashtag', label: 'Add hashtag', icon: 'i-heroicons-hashtag', click: () => alert('Add hashtag'), shortcuts: ['', 'H'] },
  { id: 'label', label: 'Add label', icon: 'i-heroicons-tag', click: () => alert('Add label'), 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
    }])

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.

<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.

<UCommandPalette loading />

Placeholder

Use the placeholder prop to change the input placeholder

<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.

<UCommandPalette :close-button="{ icon: 'i-heroicons-x-mark-20-solid', color: 'gray', variant: 'link', padded: false }" />

Empty

Use the empty-state prop to display a message when there are no results.

You can pass an object through the empty-state prop or globally through ui.commandPalette.default.emptyState. Here is the default:

You can also set it to null to hide the empty label.

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.' }" />

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 behaviour 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>
Try it yourself in this documentation's search by pressing K.

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>
The loading state will automatically be enabled when a search function is loading. You can disable this behaviour by setting the loading-icon prop to null or globally in ui.commandPalette.default.loadingIcon.

Themes

Our theming system provides a lot of flexibility to customize the component. Here is some examples of what you can do.

Algolia

Getting Started

Introduction
Installation
Theming
Shortcuts
Roadmap

Elements

Avatar
Badge
Button
Dropdown
Icon
Kbd

Forms

Input
Textarea
Select
Select Menu
Checkbox
Radio
Toggle
Form Group

Navigation

Vertical Navigation
Command Palette

Overlays

Modal
Slideover
Popover
Tooltip
Context Menu
Notification

Layout

Card
Container
Skeleton
Take a look at the component!

Raycast

Suggestions

Linear
Application
Figma
Application
Slack
Application
YouTube
Application
GitHub
Application

Commands

Clipboard History
Command
Import Extension
Command
Manage Extensions
Command
Take a look at the component!

Props

Prop Default Description
modelValuenullstring | number | Record<string, any> | unknown[]
groups[]Group[]
emptyStateappConfig.ui.commandPalette.default.emptyState{ icon: string; label: string; queryLabel: string; }
fuse{}{}
placeholder"Search..."string
iconappConfig.ui.commandPalette.default.iconstring
loadingIconappConfig.ui.commandPalette.default.loadingIconstring
by"id"string
selectedIconappConfig.ui.commandPalette.default.selectedIconstring
groupAttribute"label"string
commandAttribute"label"string
debounce200number
loadingfalseboolean
nullablefalseboolean
multiplefalseboolean
searchabletrueboolean
autoselecttrueboolean
autocleartrueboolean
uiappConfig.ui.commandPaletteany
closeButtonappConfig.ui.commandPalette.default.closeButtonany

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",
    "padding": "px-4",
    "height": "h-12",
    "size": "sm:text-sm",
    "icon": {
      "base": "pointer-events-none absolute left-4 text-gray-400 dark:text-gray-500",
      "size": "h-4 w-4",
      "padding": "pl-10"
    },
    "closeButton": "absolute right-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"
  }
}

Made by