CommandPalette

A command palette with full-text search powered by Fuse.js for efficient fuzzy matching.

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.

You can also use the @update:model-value event to listen to the selected item(s).

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:

Each group takes some items as an array of objects with the following properties:

  • prefix?: string
  • label?: string
  • suffix?: string
  • icon?: string
  • avatar?: AvatarProps
  • chip?: ChipProps
  • kbds?: string[] | KbdProps[]
  • active?: boolean
  • loading?: boolean
  • disabled?: boolean
  • slot?: string
  • onSelect?(e?: Event): void
Users
 Benjamin Canacbenjamincanac
 Sylvain Marroufinsmarroufin
 Sébastien Chopinatinux
 Romain Hamelromhml
 Haytham A. SalamaHaythamasalama
 Daniel Roedanielroe
 Neil Richternoook
<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: 'Sylvain Marroufin',
        suffix: 'smarroufin',
        avatar: {
          src: 'https://github.com/smarroufin.png'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png'
        }
      },
      {
        label: 'Haytham A. Salama',
        suffix: 'Haythamasalama',
        avatar: {
          src: 'https://github.com/Haythamasalama.png'
        }
      },
      {
        label: 'Daniel Roe',
        suffix: 'danielroe',
        avatar: {
          src: 'https://github.com/danielroe.png'
        }
      },
      {
        label: 'Neil Richter',
        suffix: 'noook',
        avatar: {
          src: 'https://github.com/noook.png'
        }
      }
    ]
  }
])
const value = ref({})
</script>

<template>
  <UCommandPalette v-model="value" :groups="groups" class="flex-1" />
</template>
You must provide an id for each group otherwise the group will be ignored.

Multiple

Use the multiple prop to allow multiple selections.

Users
 Benjamin Canacbenjamincanac
 Sylvain Marroufinsmarroufin
 Sébastien Chopinatinux
 Romain Hamelromhml
 Haytham A. SalamaHaythamasalama
 Daniel Roedanielroe
 Neil Richternoook
<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: 'Sylvain Marroufin',
        suffix: 'smarroufin',
        avatar: {
          src: 'https://github.com/smarroufin.png'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png'
        }
      },
      {
        label: 'Haytham A. Salama',
        suffix: 'Haythamasalama',
        avatar: {
          src: 'https://github.com/Haythamasalama.png'
        }
      },
      {
        label: 'Daniel Roe',
        suffix: 'danielroe',
        avatar: {
          src: 'https://github.com/danielroe.png'
        }
      },
      {
        label: 'Neil Richter',
        suffix: 'noook',
        avatar: {
          src: 'https://github.com/noook.png'
        }
      }
    ]
  }
])
const value = ref([])
</script>

<template>
  <UCommandPalette multiple v-model="value" :groups="groups" class="flex-1" />
</template>
Ensure to pass an array to the default-value prop or the v-model directive.

Placeholder

Use the placeholder prop to change the placeholder text.

Calendar
Music
Maps
<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.

Calendar
Music
Maps
<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>
You can customize this icon globally in your app.config.ts under ui.icons.search key.
You can customize this icon globally in your vite.config.ts under ui.icons.search key.

Loading

Use the loading prop to show a loading icon on the CommandPalette.

Calendar
Music
Maps
<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-refresh-cw.

Calendar
Music
Maps
<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-repeat-2" :groups="groups" class="flex-1" />
</template>
You can customize this icon globally in your app.config.ts under ui.icons.loading key.
You can customize this icon globally in your vite.config.ts under ui.icons.loading key.

Disabled

Use the disabled prop to disable the CommandPalette.

Calendar
Music
Maps
<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>

Close

Use the close prop to display a Button to dismiss the CommandPalette.

An update:open event will be emitted when the close button is clicked.
Calendar
Music
Maps
<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.

Calendar
Music
Maps
<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.

Calendar
Music
Maps
<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>
You can customize this icon globally in your app.config.ts under ui.icons.close key.
You can customize this icon globally in your vite.config.ts under ui.icons.close key.

Examples

Control selected item(s)

You can control the selected item by using the default-value prop or the v-model directive, by using the select field on each item or by using the @update:model-value event.

Users
bBenjamin Canacbenjamincanac
sSylvain Marroufinsmarroufin
aSébastien Chopinatinux
rRomain Hamelromhml
HHaytham A. SalamaHaythamasalama
dDaniel Roedanielroe
nNeil Richternoook
Add new fileCreate a new file in the current directory or workspace.
Add new folderCreate a new folder in the current directory or workspace.
Add hashtagAdd a hashtag to the current item.
Add labelAdd a label to the current item.
<script setup lang="ts">
const router = useRouter()

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',
          alt: 'benjamincanac'
        }
      },
      {
        label: 'Sylvain Marroufin',
        suffix: 'smarroufin',
        to: 'https://github.com/smarroufin',
        target: '_blank',
        avatar: {
          src: 'https://github.com/smarroufin.png',
          alt: 'smarroufin'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        to: 'https://github.com/atinux',
        target: '_blank',
        avatar: {
          src: 'https://github.com/atinux.png',
          alt: 'atinux'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        to: 'https://github.com/romhml',
        target: '_blank',
        avatar: {
          src: 'https://github.com/romhml.png',
          alt: 'romhml'
        }
      },
      {
        label: 'Haytham A. Salama',
        suffix: 'Haythamasalama',
        to: 'https://github.com/Haythamasalama',
        target: '_blank',
        avatar: {
          src: 'https://github.com/Haythamasalama.png',
          alt: 'Haythamasalama'
        }
      },
      {
        label: 'Daniel Roe',
        suffix: 'danielroe',
        to: 'https://github.com/danielroe',
        target: '_blank',
        avatar: {
          src: 'https://github.com/danielroe.png',
          alt: 'danielroe'
        }
      },
      {
        label: 'Neil Richter',
        suffix: 'noook',
        to: 'https://github.com/noook',
        target: '_blank',
        avatar: {
          src: 'https://github.com/noook.png',
          alt: 'noook'
        }
      }
    ]
  },
  {
    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() {
          console.log('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() {
          console.log('Add new folder')
        }
      },
      {
        label: 'Add hashtag',
        suffix: 'Add a hashtag to the current item.',
        icon: 'i-lucide-hash',
        kbds: [
          'meta',
          'H'
        ],
        onSelect() {
          console.log('Add hashtag')
        }
      },
      {
        label: 'Add label',
        suffix: 'Add a label to the current item.',
        icon: 'i-lucide-tag',
        kbds: [
          'meta',
          'L'
        ],
        onSelect() {
          console.log('Add label')
        }
      }
    ]
  }
])

function onSelect(item: any) {
  if (item.onSelect) {
    item.onSelect()
  } else if (item.to) {
    if (typeof item.to === 'string' && (item.target === '_blank' || item.to.startsWith('http'))) {
      window.open(item.to, item.target || '_blank')
    } else {
      router.push(item.to)
    }
  }
}
</script>

<template>
  <UCommandPalette
    :groups="groups"
    class="flex-1 h-80"
    @update:model-value="onSelect"
  />
</template>
This example demonstrates how to use the @update:model-value event to handle different selection scenarios.

Control search term

Use the v-model:search-term directive to control the search term.

bBenjamin Canacbenjamincanac
aSébastien Chopinatinux
<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',
      alt: 'benjamincanac'
    }
  },
  {
    label: 'Sylvain Marroufin',
    suffix: 'smarroufin',
    to: 'https://github.com/smarroufin',
    target: '_blank',
    avatar: {
      src: 'https://github.com/smarroufin.png',
      alt: 'smarroufin'
    }
  },
  {
    label: 'Sébastien Chopin',
    suffix: 'atinux',
    to: 'https://github.com/atinux',
    target: '_blank',
    avatar: {
      src: 'https://github.com/atinux.png',
      alt: 'atinux'
    }
  },
  {
    label: 'Romain Hamel',
    suffix: 'romhml',
    to: 'https://github.com/romhml',
    target: '_blank',
    avatar: {
      src: 'https://github.com/romhml.png',
      alt: 'romhml'
    }
  },
  {
    label: 'Haytham A. Salama',
    suffix: 'Haythamasalama',
    to: 'https://github.com/Haythamasalama',
    target: '_blank',
    avatar: {
      src: 'https://github.com/Haythamasalama.png',
      alt: 'Haythamasalama'
    }
  },
  {
    label: 'Daniel Roe',
    suffix: 'danielroe',
    to: 'https://github.com/danielroe',
    target: '_blank',
    avatar: {
      src: 'https://github.com/danielroe.png',
      alt: 'danielroe'
    }
  },
  {
    label: 'Neil Richter',
    suffix: 'noook',
    to: 'https://github.com/noook',
    target: '_blank',
    avatar: {
      src: 'https://github.com/noook.png',
      alt: 'noook'
    }
  }
]

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>
This example uses the @update:model-value event to reset the search term when an item is selected.

With fetched items

You can fetch items from an API and use them in the CommandPalette.

Users
 Leanne GrahamSincere@april.biz
 Ervin HowellShanna@melissa.tv
 Clementine BauchNathan@yesenia.net
 Patricia LebsackJulianne.OConner@kory.org
 Chelsey DietrichLucio_Hettinger@annie.ca
 Mrs. Dennis SchulistKarley_Dach@jasper.info
 Kurtis WeissnatTelly.Hoeger@billy.biz
 Nicholas Runolfsdottir VSherwood@rosamond.me
 Glenna ReichertChaim_McDermott@dana.io
 Clementina DuBuqueRey.Padberg@karina.biz
<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>

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.

Users
 Leanne GrahamSincere@april.biz
 Ervin HowellShanna@melissa.tv
 Clementine BauchNathan@yesenia.net
 Patricia LebsackJulianne.OConner@kory.org
 Chelsey DietrichLucio_Hettinger@annie.ca
 Mrs. Dennis SchulistKarley_Dach@jasper.info
 Kurtis WeissnatTelly.Hoeger@billy.biz
 Nicholas Runolfsdottir VSherwood@rosamond.me
 Glenna ReichertChaim_McDermott@dana.io
 Clementina DuBuqueRey.Padberg@karina.biz
<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 || [],
  ignoreFilter: true
}])
</script>

<template>
  <UCommandPalette
    v-model:search-term="searchTerm"
    :loading="status === 'pending'"
    :groups="groups"
    class="flex-1 h-80"
  />
</template>
This example uses 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.

Introduction
Installation
<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#reka-ui-radix-vue',
    label: 'Reka UI',
    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>
Start typing to see items with higher level appear.

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

 Leanne GrahamSincere@april.biz
 Ervin HowellShanna@melissa.tv
 Clementine BauchNathan@yesenia.net
 Patricia LebsackJulianne.OConner@kory.org
 Chelsey DietrichLucio_Hettinger@annie.ca
 Mrs. Dennis SchulistKarley_Dach@jasper.info
 Kurtis WeissnatTelly.Hoeger@billy.biz
 Nicholas Runolfsdottir VSherwood@rosamond.me
 Glenna ReichertChaim_McDermott@dana.io
 Clementina DuBuqueRey.Padberg@karina.biz
<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-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' }"
      />
    </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 || [],
  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', {
  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',
    avatar: {
      src: 'https://github.com/benjamincanac.png'
    }
  },
  {
    label: 'Sylvain Marroufin',
    suffix: 'smarroufin',
    avatar: {
      src: 'https://github.com/smarroufin.png'
    }
  },
  {
    label: 'Sébastien Chopin',
    suffix: 'atinux',
    avatar: {
      src: 'https://github.com/atinux.png'
    }
  },
  {
    label: 'Romain Hamel',
    suffix: 'romhml',
    avatar: {
      src: 'https://github.com/romhml.png'
    }
  },
  {
    label: 'Haytham A. Salama',
    suffix: 'Haythamasalama',
    avatar: {
      src: 'https://github.com/Haythamasalama.png'
    }
  },
  {
    label: 'Daniel Roe',
    suffix: 'danielroe',
    avatar: {
      src: 'https://github.com/danielroe.png'
    }
  },
  {
    label: 'Neil Richter',
    suffix: 'noook',
    avatar: {
      src: 'https://github.com/noook.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>
This can be useful when using the CommandPalette inside a Modal for example.

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
Profile
Billing 50% off
Notifications
Security
Users
 Benjamin Canacbenjamincanac
 Sylvain Marroufinsmarroufin
 Sébastien Chopinatinux
 Romain Hamelromhml
 Haytham A. SalamaHaythamasalama
 Daniel Roedanielroe
 Neil Richternoook
<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'
    },
    {
      label: 'Notifications',
      icon: 'i-lucide-bell'
    },
    {
      label: 'Security',
      icon: 'i-lucide-lock'
    }
  ]
}, {
  id: 'users',
  label: 'Users',
  slot: 'users',
  items: [
    {
      label: 'Benjamin Canac',
      suffix: 'benjamincanac'
    },
    {
      label: 'Sylvain Marroufin',
      suffix: 'smarroufin'
    },
    {
      label: 'Sébastien Chopin',
      suffix: 'atinux'
    },
    {
      label: 'Romain Hamel',
      suffix: 'romhml'
    },
    {
      label: 'Haytham A. Salama',
      suffix: 'Haythamasalama'
    },
    {
      label: 'Daniel Roe',
      suffix: 'danielroe'
    },
    {
      label: 'Neil Richter',
      suffix: 'noook'
    }
  ]
}]
</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 }">
      {{ item.label }}

      <UBadge variant="subtle" size="sm">
        50% off
      </UBadge>
    </template>
  </UCommandPalette>
</template>
You can also use the #item, #item-leading, #item-label and #item-trailing slots to customize all items.

API

Props

Prop Default Type
as

'div'

any

The element or component this component should render as.

searchTerm

string

icon

appConfig.ui.icons.search

string

The icon displayed in the input.

selectedIcon

appConfig.ui.icons.check

string

The icon displayed when an item is selected.

placeholder

'Type a command or search...'

string

The placeholder text for the input.

autofocus

true

boolean

Automatically focus the input when component is mounted.

close

false

boolean | ButtonProps

Display a close button in the input (useful when inside a Modal for example). { size: 'md', color: 'neutral', variant: 'ghost' }

closeIcon

appConfig.ui.icons.close

string

The icon displayed in the close button.

groups

CommandPaletteGroup<CommandPaletteItem>[]

fuse

{ fuseOptions: { ignoreLocation: true, threshold: 0.1, keys: ['label', 'suffix'] }, resultLimit: 12, matchAllWhenSearchEmpty: true }

UseFuseOptions<CommandPaletteItem>

Options for useFuse.

labelKey

'label'

string

The key used to get the label from the item.

disabled

boolean

When true, prevents the user from interacting with listbox

multiple

boolean

Whether multiple options can be selected or not.

defaultValue

null | string | number | Record<string, any> | AcceptableValue[]

The value of the listbox when initially rendered. Use when you do not need to control the state of the Listbox

modelValue

''

null | string | number | Record<string, any> | AcceptableValue[]

The controlled value of the listbox. Can be binded with with v-model.

highlightOnHover

boolean

When true, hover over item will trigger highlight

loading

boolean

When true, the loading icon will be displayed.

loadingIcon

appConfig.ui.icons.loading

string

The icon when the loading prop is true.

ui

PartialString<{ root: string; input: string; close: string; content: string; viewport: string; group: string; empty: string; label: string; item: string; itemLeadingIcon: string; itemLeadingAvatar: string; itemLeadingAvatarSize: string; ... 10 more ...; itemLabelSuffix: string; }>

Slots

Slot Type
empty

{ searchTerm?: string | undefined; }

close

{ ui: any; }

item

{ item: CommandPaletteItem; index: number; }

item-leading

{ item: CommandPaletteItem; index: number; }

item-label

{ item: CommandPaletteItem; index: number; }

item-trailing

{ item: CommandPaletteItem; index: number; }

Emits

Event Type
update:open

[value: boolean]

update:modelValue

[value: CommandPaletteItem]

highlight

[payload: { ref: HTMLElement; value: CommandPaletteItem; } | undefined]

entryFocus

[event: CustomEvent<any>]

leave

[event: Event]

update:searchTerm

[value: string]

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    commandPalette: {
      slots: {
        root: 'flex flex-col min-h-0 min-w-0 divide-y divide-[var(--ui-border)]',
        input: '[&>input]:h-12',
        close: '',
        content: 'relative overflow-hidden flex flex-col',
        viewport: 'relative divide-y divide-[var(--ui-border)] scroll-py-1 overflow-y-auto flex-1 focus:outline-none',
        group: 'p-1 isolate',
        empty: 'py-6 text-center text-sm text-[var(--ui-text-muted)]',
        label: 'px-2 py-1.5 text-xs font-semibold text-[var(--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-[calc(var(--ui-radius)*1.5)] 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-[var(--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 rtl:space-x-reverse',
        itemLabelBase: 'text-[var(--ui-text-highlighted)] [&>mark]:text-[var(--ui-bg)] [&>mark]:bg-[var(--ui-primary)]',
        itemLabelPrefix: 'text-[var(--ui-text)]',
        itemLabelSuffix: 'text-[var(--ui-text-dimmed)] [&>mark]:text-[var(--ui-bg)] [&>mark]:bg-[var(--ui-primary)]'
      },
      variants: {
        active: {
          true: {
            item: 'text-[var(--ui-text-highlighted)] before:bg-[var(--ui-bg-elevated)]',
            itemLeadingIcon: 'text-[var(--ui-text)]'
          },
          false: {
            item: [
              'text-[var(--ui-text)] data-highlighted:text-[var(--ui-text-highlighted)] data-highlighted:before:bg-[var(--ui-bg-elevated)]/50',
              'transition-colors before:transition-colors'
            ],
            itemLeadingIcon: [
              'text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)]',
              'transition-colors'
            ]
          }
        },
        loading: {
          true: {
            itemLeadingIcon: 'animate-spin'
          }
        }
      }
    }
  }
})
vite.config.ts
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-[var(--ui-border)]',
            input: '[&>input]:h-12',
            close: '',
            content: 'relative overflow-hidden flex flex-col',
            viewport: 'relative divide-y divide-[var(--ui-border)] scroll-py-1 overflow-y-auto flex-1 focus:outline-none',
            group: 'p-1 isolate',
            empty: 'py-6 text-center text-sm text-[var(--ui-text-muted)]',
            label: 'px-2 py-1.5 text-xs font-semibold text-[var(--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-[calc(var(--ui-radius)*1.5)] 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-[var(--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 rtl:space-x-reverse',
            itemLabelBase: 'text-[var(--ui-text-highlighted)] [&>mark]:text-[var(--ui-bg)] [&>mark]:bg-[var(--ui-primary)]',
            itemLabelPrefix: 'text-[var(--ui-text)]',
            itemLabelSuffix: 'text-[var(--ui-text-dimmed)] [&>mark]:text-[var(--ui-bg)] [&>mark]:bg-[var(--ui-primary)]'
          },
          variants: {
            active: {
              true: {
                item: 'text-[var(--ui-text-highlighted)] before:bg-[var(--ui-bg-elevated)]',
                itemLeadingIcon: 'text-[var(--ui-text)]'
              },
              false: {
                item: [
                  'text-[var(--ui-text)] data-highlighted:text-[var(--ui-text-highlighted)] data-highlighted:before:bg-[var(--ui-bg-elevated)]/50',
                  'transition-colors before:transition-colors'
                ],
                itemLeadingIcon: [
                  'text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)]',
                  'transition-colors'
                ]
              }
            },
            loading: {
              true: {
                itemLeadingIcon: 'animate-spin'
              }
            }
          }
        }
      }
    })
  ]
})