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.

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref({})
</script>

<template>
  <PCommandPalette v-model="value" :groups="groups" class="flex-1 h-80" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref({})
</script>

<template>
  <PCommandPalette v-model="value" :groups="groups" class="flex-1 h-80" />
</template>
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:

You must provide an 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?: string
  • label?: string
  • suffix?: string
  • icon?: string
  • avatar?: AvatarProps
  • chip?: ChipProps
  • kbds?: string[] | KbdProps[]
  • active?: boolean
  • loading?: boolean
  • disabled?: boolean
  • slot?: string
  • placeholder?: string
  • children?: CommandPaletteItem[]
  • onSelect?: (e: Event) => void
  • class?: any
  • ui?: { 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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref({})
</script>

<template>
  <PCommandPalette v-model="value" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref({})
</script>

<template>
  <PCommandPalette v-model="value" :groups="groups" class="flex-1" />
</template>
Each item can take a children array of objects with the following properties to create submenus:

Multiple

Use the multiple prop to allow multiple selections.

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref([])
</script>

<template>
  <PCommandPalette multiple v-model="value" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref([])
</script>

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

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette placeholder="Search an app..." :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette placeholder="Search an app..." :groups="groups" class="flex-1" />
</template>

Size

Use the size prop to change the size of the CommandPalette.

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette size="xl" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette size="xl" :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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette icon="i-lucide-box" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

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

Selected Icon

Use the selected-icon prop to customize the selected item Icon. Defaults to i-lucide-check.

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref([
  {
    label: 'Benjamin Canac',
    suffix: 'benjamincanac',
    avatar: {
      src: 'https://github.com/benjamincanac.png',
      loading: 'lazy'
    }
  }
])
</script>

<template>
  <PCommandPalette multiple v-model="value" selected-icon="i-lucide-circle-check" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'users',
    label: 'Users',
    items: [
      {
        label: 'Benjamin Canac',
        suffix: 'benjamincanac',
        avatar: {
          src: 'https://github.com/benjamincanac.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy'
        }
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy'
        }
      }
    ]
  }
])
const value = ref([
  {
    label: 'Benjamin Canac',
    suffix: 'benjamincanac',
    avatar: {
      src: 'https://github.com/benjamincanac.png',
      loading: 'lazy'
    }
  }
])
</script>

<template>
  <PCommandPalette multiple v-model="value" selected-icon="i-lucide-circle-check" :groups="groups" class="flex-1" />
</template>
You can customize this icon globally in your app.config.ts under ui.icons.check key.
You can customize this icon globally in your vite.config.ts under ui.icons.check key.

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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    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>
  <PCommandPalette trailing-icon="i-lucide-arrow-right" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    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>
  <PCommandPalette trailing-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.chevronRight key.
You can customize this icon globally in your vite.config.ts under ui.icons.chevronRight key.

Loading

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

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette loading :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette 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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette loading loading-icon="i-lucide-loader" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette loading loading-icon="i-lucide-loader" :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.

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.
<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette close :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette close :groups="groups" class="flex-1" />
</template>

You can pass any property from the Button component to customize it.

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette
    :close="{
      color: 'primary',
      variant: 'outline',
      class: 'rounded-full'
    }"
    :groups="groups"
    class="flex-1"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette
    :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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette close close-icon="i-lucide-arrow-right" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

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

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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    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>
  <PCommandPalette
    :back="{
      color: 'primary'
    }"
    :groups="groups"
    class="flex-1"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    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>
  <PCommandPalette
    :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">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    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>
  <PCommandPalette back back-icon="i-lucide-house" :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    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>
  <PCommandPalette back back-icon="i-lucide-house" :groups="groups" class="flex-1" />
</template>
You can customize this icon globally in your app.config.ts under ui.icons.arrowLeft key.
You can customize this icon globally in your vite.config.ts under ui.icons.arrowLeft key.

Disabled

Use the disabled prop to disable the CommandPalette.

<script setup lang="ts">
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette disabled :groups="groups" class="flex-1" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteGroup } from 'pohon-ui'

const groups = ref<CommandPaletteGroup[]>([
  {
    id: 'apps',
    items: [
      {
        label: 'Calendar',
        icon: 'i-lucide-calendar'
      },
      {
        label: 'Music',
        icon: 'i-lucide-music'
      },
      {
        label: 'Maps',
        icon: 'i-lucide-map'
      }
    ]
  }
])
</script>

<template>
  <PCommandPalette 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',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        to: 'https://github.com/HugoRCD',
        target: '_blank',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        to: 'https://github.com/atinux',
        target: '_blank',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        to: 'https://github.com/romhml',
        target: '_blank',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        to: 'https://github.com/sandros94',
        target: '_blank',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        to: 'https://github.com/J-Michalek',
        target: '_blank',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        to: 'https://github.com/hywax',
        target: '_blank',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        to: 'https://github.com/maximepvrt',
        target: '_blank',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy' as const,
        },
      },
    ],
  },
  {
    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>
  <PCommandPalette
    :groups="groups"
    class="flex-1 h-80"
    @update:model-value="onSelect"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

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',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Hugo Richard',
        suffix: 'HugoRCD',
        to: 'https://github.com/HugoRCD',
        target: '_blank',
        avatar: {
          src: 'https://github.com/HugoRCD.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Sébastien Chopin',
        suffix: 'atinux',
        to: 'https://github.com/atinux',
        target: '_blank',
        avatar: {
          src: 'https://github.com/atinux.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Romain Hamel',
        suffix: 'romhml',
        to: 'https://github.com/romhml',
        target: '_blank',
        avatar: {
          src: 'https://github.com/romhml.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Sandro Circi',
        suffix: 'sandros94',
        to: 'https://github.com/sandros94',
        target: '_blank',
        avatar: {
          src: 'https://github.com/sandros94.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Jakub Michálek',
        suffix: 'J-Michalek',
        to: 'https://github.com/J-Michalek',
        target: '_blank',
        avatar: {
          src: 'https://github.com/J-Michalek.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Alex',
        suffix: 'hywax',
        to: 'https://github.com/hywax',
        target: '_blank',
        avatar: {
          src: 'https://github.com/hywax.png',
          loading: 'lazy' as const,
        },
      },
      {
        label: 'Maxime Pauvert',
        suffix: 'maximepvrt',
        to: 'https://github.com/maximepvrt',
        target: '_blank',
        avatar: {
          src: 'https://github.com/maximepvrt.png',
          loading: 'lazy' as const,
        },
      },
    ],
  },
  {
    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>
  <PCommandPalette
    :groups="groups"
    class="flex-1 h-80"
    @update:model-value="onSelect"
  />
</template>
Use the value-key prop to select a field of an item to use as the value instead of the object itself. Use the by prop to compare objects by a field instead of reference.

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',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Hugo Richard',
    suffix: 'HugoRCD',
    to: 'https://github.com/HugoRCD',
    target: '_blank',
    avatar: {
      src: 'https://github.com/HugoRCD.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sébastien Chopin',
    suffix: 'atinux',
    to: 'https://github.com/atinux',
    target: '_blank',
    avatar: {
      src: 'https://github.com/atinux.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Romain Hamel',
    suffix: 'romhml',
    to: 'https://github.com/romhml',
    target: '_blank',
    avatar: {
      src: 'https://github.com/romhml.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sandro Circi',
    suffix: 'sandros94',
    to: 'https://github.com/sandros94',
    target: '_blank',
    avatar: {
      src: 'https://github.com/sandros94.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Jakub Michálek',
    suffix: 'J-Michalek',
    to: 'https://github.com/J-Michalek',
    target: '_blank',
    avatar: {
      src: 'https://github.com/J-Michalek.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Alex',
    suffix: 'hywax',
    to: 'https://github.com/hywax',
    target: '_blank',
    avatar: {
      src: 'https://github.com/hywax.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Maxime Pauvert',
    suffix: 'maximepvrt',
    to: 'https://github.com/maximepvrt',
    target: '_blank',
    avatar: {
      src: 'https://github.com/maximepvrt.png',
      loading: 'lazy' as const,
    },
  },
];

const searchTerm = ref('B');

function onSelect() {
  searchTerm.value = '';
}
</script>

<template>
  <PCommandPalette
    v-model:search-term="searchTerm"
    :groups="[{ id: 'users', items: users }]"
    class="flex-1"
    @update:model-value="onSelect"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const users = [
  {
    label: 'Benjamin Canac',
    suffix: 'benjamincanac',
    to: 'https://github.com/benjamincanac',
    target: '_blank',
    avatar: {
      src: 'https://github.com/benjamincanac.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Hugo Richard',
    suffix: 'HugoRCD',
    to: 'https://github.com/HugoRCD',
    target: '_blank',
    avatar: {
      src: 'https://github.com/HugoRCD.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sébastien Chopin',
    suffix: 'atinux',
    to: 'https://github.com/atinux',
    target: '_blank',
    avatar: {
      src: 'https://github.com/atinux.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Romain Hamel',
    suffix: 'romhml',
    to: 'https://github.com/romhml',
    target: '_blank',
    avatar: {
      src: 'https://github.com/romhml.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sandro Circi',
    suffix: 'sandros94',
    to: 'https://github.com/sandros94',
    target: '_blank',
    avatar: {
      src: 'https://github.com/sandros94.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Jakub Michálek',
    suffix: 'J-Michalek',
    to: 'https://github.com/J-Michalek',
    target: '_blank',
    avatar: {
      src: 'https://github.com/J-Michalek.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Alex',
    suffix: 'hywax',
    to: 'https://github.com/hywax',
    target: '_blank',
    avatar: {
      src: 'https://github.com/hywax.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Maxime Pauvert',
    suffix: 'maximepvrt',
    to: 'https://github.com/maximepvrt',
    target: '_blank',
    avatar: {
      src: 'https://github.com/maximepvrt.png',
      loading: 'lazy' as const,
    },
  },
];

const searchTerm = ref('B');

function onSelect() {
  searchTerm.value = '';
}
</script>

<template>
  <PCommandPalette
    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 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>
  <PCommandPalette :groups="groups" class="flex-1" />
</template>
When navigating into a submenu:
  • 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.

No data
<script setup lang="ts">
const searchTerm = ref('');

const { data: users, status } = useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'command-palette-users',
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  server: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
}]);
</script>

<template>
  <PCommandPalette
    v-model:search-term="searchTerm"
    :loading="status === 'pending' || status === 'idle'"
    :groups="groups"
    class="flex-1 h-80"
  />
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'

const searchTerm = ref('');

const { data: users, status } = useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'command-palette-users',
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  server: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
}]);
</script>

<template>
  <PCommandPalette
    v-model:search-term="searchTerm"
    :loading="status === 'pending' || status === 'idle'"
    :groups="groups"
    class="flex-1 h-80"
  />
</template>
This example uses useLazyFetch with server: false to fetch data on the client without blocking the initial render. The loading state checks for both pending and idle status to display a loading indicator before and during the fetch.

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.

No data
<script setup lang="ts">
import { refDebounced } from '@vueuse/core';

const searchTerm = ref('');
const searchTermDebounced = refDebounced(searchTerm, 200);

const { data: users, status } = useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'command-palette-users-search',
  query: { q: searchTermDebounced },
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  server: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true,
}]);
</script>

<template>
  <PCommandPalette
    v-model:search-term="searchTerm"
    :loading="status === 'pending' || status === 'idle'"
    :groups="groups"
    class="flex-1 h-80"
  />
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { refDebounced } from '@vueuse/core';

const searchTerm = ref('');
const searchTermDebounced = refDebounced(searchTerm, 200);

const { data: users, status } = useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'command-palette-users-search',
  query: { q: searchTermDebounced },
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  server: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true,
}]);
</script>

<template>
  <PCommandPalette
    v-model:search-term="searchTerm"
    :loading="status === 'pending' || status === 'idle'"
    :groups="groups"
    class="flex-1 h-80"
  />
</template>
This example uses refDebounced to debounce the API calls. The loading state checks for both pending and idle status to display a loading indicator before and during the fetch.

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: '/pohon/getting-started#whats-new-in-v3',
    label: 'What\'s new in v3?',
    level: 2,
  },
  {
    id: '/pohon/getting-started#akar',
    label: 'Akar',
    level: 3,
  },
  {
    id: '/pohon/getting-started/installation/nuxt',
    label: 'UnoCSS',
    level: 3,
  },
  {
    id: '/pohon/getting-started#unocss-variants',
    label: 'UnoCSS Variants',
    level: 3,
  },
  {
    id: '/pohon/getting-started/installation',
    label: 'Installation',
    level: 1,
  },
];

function postFilter(searchTerm: string, items: Array<any>) {
  // Filter only first level items if no searchTerm
  if (!searchTerm) {
    return items?.filter((item) => item.level === 1);
  }

  return items;
}
</script>

<template>
  <PCommandPalette
    :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', 'description', '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.

No data
<script setup lang="ts">
const { data: users } = useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'command-palette-users',
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  server: false,
});
</script>

<template>
  <PCommandPalette
    :groups="[{ id: 'users', items: users || [] }]"
    :fuse="{ fuseOptions: { includeMatches: true } }"
    class="flex-1 h-80"
  />
</template>

With virtualization

Use the virtualize prop to enable virtualization for large lists as a boolean or an object with options like { estimateSize: 32, overscan: 12 }.

When enabled, all groups are flattened into a single list due to a limitation of Akar.
<script setup lang="ts">
import type { CommandPaletteItem } from 'pohon-ui';

const items: Array<CommandPaletteItem> = Array(1000)
  .fill(0)
  .map((_, value) => ({
    label: `item-${value}`,
    value,
  }));

const groups = [
  {
    id: 'items',
    items,
  },
];
</script>

<template>
  <PCommandPalette
    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">
import type { CommandPaletteItem } from 'pohon-ui';

const items = ref([
  {
    label: 'bug',
    value: 'bug',
    chip: {
      color: 'error',
    },
  },
  {
    label: 'feature',
    value: 'feature',
    chip: {
      color: 'success',
    },
  },
  {
    label: 'enhancement',
    value: 'enhancement',
    chip: {
      color: 'info',
    },
  },
] satisfies Array<CommandPaletteItem>);

const label = ref([]);
</script>

<template>
  <PPopover :content="{ side: 'right', align: 'start' }">
    <PButton
      icon="i-lucide-tag"
      label="Select labels"
      color="neutral"
      variant="subtle"
    />

    <template #content>
      <PCommandPalette
        v-model="label"
        multiple
        placeholder="Search labels..."
        :groups="[{ id: 'labels', items }]"
        :ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }"
      />
    </template>
  </PPopover>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { CommandPaletteItem } from 'pohon-ui';

const items = ref([
  {
    label: 'bug',
    value: 'bug',
    chip: {
      color: 'error',
    },
  },
  {
    label: 'feature',
    value: 'feature',
    chip: {
      color: 'success',
    },
  },
  {
    label: 'enhancement',
    value: 'enhancement',
    chip: {
      color: 'info',
    },
  },
] satisfies Array<CommandPaletteItem>);

const label = ref([]);
</script>

<template>
  <PPopover :content="{ side: 'right', align: 'start' }">
    <PButton
      icon="i-lucide-tag"
      label="Select labels"
      color="neutral"
      variant="subtle"
    />

    <template #content>
      <PCommandPalette
        v-model="label"
        multiple
        placeholder="Search labels..."
        :groups="[{ id: 'labels', items }]"
        :ui="{ input: '[&>input]:h-8 [&>input]:text-sm' }"
      />
    </template>
  </PPopover>
</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, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'modal-command-palette-users',
  query: { q: searchTerm },
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  immediate: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true,
}]);

function onOpen() {
  if (!users.value?.length) {
    execute();
  }
}
</script>

<template>
  <PModal @update:open="onOpen">
    <PButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <PCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </PModal>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'

const searchTerm = ref('');

const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'modal-command-palette-users',
  query: { q: searchTerm },
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  immediate: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true,
}]);

function onOpen() {
  if (!users.value?.length) {
    execute();
  }
}
</script>

<template>
  <PModal @update:open="onOpen">
    <PButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <PCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </PModal>
</template>
This example uses useLazyFetch with immediate: false to only fetch data when the Modal opens.

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, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'drawer-command-palette-users',
  query: { q: searchTerm },
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  immediate: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true,
}]);

function onOpen() {
  if (!users.value?.length) {
    execute();
  }
}
</script>

<template>
  <PDrawer
    :handle="false"
    @update:open="onOpen"
  >
    <PButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <PCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </PDrawer>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'

const searchTerm = ref('');

const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'drawer-command-palette-users',
  query: { q: searchTerm },
  transform: (data: Array<{ 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}`, loading: 'lazy' as const } })) || [];
  },
  immediate: false,
});

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true,
}]);

function onOpen() {
  if (!users.value?.length) {
    execute();
  }
}
</script>

<template>
  <PDrawer
    :handle="false"
    @update:open="onOpen"
  >
    <PButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <PCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </PDrawer>
</template>
This example uses useLazyFetch with immediate: false to only fetch data when the Drawer opens.

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',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Hugo Richard',
    suffix: 'HugoRCD',
    to: 'https://github.com/HugoRCD',
    target: '_blank',
    avatar: {
      src: 'https://github.com/HugoRCD.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sébastien Chopin',
    suffix: 'atinux',
    to: 'https://github.com/atinux',
    target: '_blank',
    avatar: {
      src: 'https://github.com/atinux.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Romain Hamel',
    suffix: 'romhml',
    to: 'https://github.com/romhml',
    target: '_blank',
    avatar: {
      src: 'https://github.com/romhml.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sandro Circi',
    suffix: 'sandros94',
    to: 'https://github.com/sandros94',
    target: '_blank',
    avatar: {
      src: 'https://github.com/sandros94.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Jakub Michálek',
    suffix: 'J-Michalek',
    to: 'https://github.com/J-Michalek',
    target: '_blank',
    avatar: {
      src: 'https://github.com/J-Michalek.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Alex',
    suffix: 'hywax',
    to: 'https://github.com/hywax',
    target: '_blank',
    avatar: {
      src: 'https://github.com/hywax.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Maxime Pauvert',
    suffix: 'maximepvrt',
    to: 'https://github.com/maximepvrt',
    target: '_blank',
    avatar: {
      src: 'https://github.com/maximepvrt.png',
      loading: 'lazy' as const,
    },
  },
];
</script>

<template>
  <PModal v-model:open="open">
    <PButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <PCommandPalette
        close
        :groups="[{ id: 'users', items: users }]"
        @update:open="open = $event"
      />
    </template>
  </PModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'

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',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Hugo Richard',
    suffix: 'HugoRCD',
    to: 'https://github.com/HugoRCD',
    target: '_blank',
    avatar: {
      src: 'https://github.com/HugoRCD.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sébastien Chopin',
    suffix: 'atinux',
    to: 'https://github.com/atinux',
    target: '_blank',
    avatar: {
      src: 'https://github.com/atinux.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Romain Hamel',
    suffix: 'romhml',
    to: 'https://github.com/romhml',
    target: '_blank',
    avatar: {
      src: 'https://github.com/romhml.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Sandro Circi',
    suffix: 'sandros94',
    to: 'https://github.com/sandros94',
    target: '_blank',
    avatar: {
      src: 'https://github.com/sandros94.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Jakub Michálek',
    suffix: 'J-Michalek',
    to: 'https://github.com/J-Michalek',
    target: '_blank',
    avatar: {
      src: 'https://github.com/J-Michalek.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Alex',
    suffix: 'hywax',
    to: 'https://github.com/hywax',
    target: '_blank',
    avatar: {
      src: 'https://github.com/hywax.png',
      loading: 'lazy' as const,
    },
  },
  {
    label: 'Maxime Pauvert',
    suffix: 'maximepvrt',
    to: 'https://github.com/maximepvrt',
    target: '_blank',
    avatar: {
      src: 'https://github.com/maximepvrt.png',
      loading: 'lazy' as const,
    },
  },
];
</script>

<template>
  <PModal v-model:open="open">
    <PButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <PCommandPalette
        close
        :groups="[{ id: 'users', items: users }]"
        @update:open="open = $event"
      />
    </template>
  </PModal>
</template>
This can be useful when using the CommandPalette inside a Modal for example.

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>
  <PCommandPalette
    :groups="groups"
    class="flex-1 h-80"
  >
    <template #footer>
      <div class="flex gap-2 items-center justify-between">
        <PIcon
          name="i-simple-icons-nuxtdotjs"
          class="color-text-dimmed ml-1 size-5"
        />
        <div class="flex gap-1 items-center">
          <PButton
            color="neutral"
            variant="ghost"
            label="Open Command"
            class="color-text-dimmed"
            size="xs"
          >
            <template #trailing>
              <PKbd value="enter" />
            </template>
          </PButton>
          <PSeparator
            orientation="vertical"
            class="h-4"
          />
          <PButton
            color="neutral"
            variant="ghost"
            label="Actions"
            class="color-text-dimmed"
            size="xs"
          >
            <template #trailing>
              <PKbd value="meta" />
              <PKbd value="k" />
            </template>
          </PButton>
        </div>
      </div>
    </template>
  </PCommandPalette>
</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>
  <PCommandPalette
    :groups="groups"
    class="flex-1 h-80"
  >
    <template #users-leading="{ item }">
      <PAvatar
        :src="`https://github.com/${item.suffix}.png`"
        size="2xs"
        loading="lazy"
      />
    </template>

    <template #billing-label="{ item }">
      <span class="color-primary font-medium">{{ item.label }}</span>

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

API

Props

Prop Default Type

Slots

Slot Type

Emits

Event Type

Theme

app.config.ts
export default defineAppConfig({
  ui: {
    commandPalette: {
      slots: {
        root: 'flex flex-col min-h-0 min-w-0 divide-y divide-border',
        input: '',
        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: 'text-center color-text-muted',
        label: 'font-600 color-text-highlighted',
        item: 'group outline-none flex w-full select-none items-start relative before:rounded-md before:content-empty before:inset-px before:absolute before:-z-1 data-[disabled]:opacity-75 data-[disabled]:cursor-not-allowed',
        itemLeadingIcon: 'shrink-0',
        itemLeadingAvatar: 'shrink-0',
        itemLeadingAvatarSize: '',
        itemLeadingChip: 'shrink-0',
        itemLeadingChipSize: '',
        itemTrailing: 'ms-auto inline-flex items-center',
        itemTrailingIcon: 'shrink-0',
        itemTrailingHighlightedIcon: 'color-text-dimmed shrink-0 hidden group-data-[highlighted]:inline-flex',
        itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0',
        itemTrailingKbdsSize: '',
        itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
        itemLabel: 'truncate space-x-1 color-text-dimmed',
        itemLabelBase: 'color-text-highlighted [&>mark]:color-primary [&>mark]:bg-primary/15',
        itemLabelPrefix: 'color-text',
        itemLabelSuffix: 'color-text-dimmed [&>mark]:color-primary [&>mark]:bg-primary/15',
        itemDescription: 'truncate color-text-muted [&>mark]:color-primary [&>mark]:bg-primary/15'
      },
      variants: {
        virtualize: {
          true: {
            viewport: 'p-1 isolate'
          },
          false: {
            viewport: 'divide-y divide-border'
          }
        },
        size: {
          xs: {
            input: '[&>input]:h-10',
            empty: 'py-3 text-xs',
            label: 'p-1 text-[10px]/3 gap-1',
            item: 'p-1 text-xs gap-1',
            itemLeadingIcon: 'size-4',
            itemLeadingAvatarSize: '3xs',
            itemLeadingChip: 'size-4',
            itemLeadingChipSize: 'sm',
            itemTrailing: 'gap-1',
            itemTrailingIcon: 'size-4',
            itemTrailingHighlightedIcon: 'size-4',
            itemTrailingKbds: 'gap-0.5',
            itemTrailingKbdsSize: 'sm'
          },
          sm: {
            input: '[&>input]:h-11',
            empty: 'py-4 text-xs',
            label: 'p-1.5 text-[10px]/3 gap-1.5',
            item: 'p-1.5 text-xs gap-1.5',
            itemLeadingIcon: 'size-4',
            itemLeadingAvatarSize: '3xs',
            itemLeadingChip: 'size-4',
            itemLeadingChipSize: 'sm',
            itemTrailing: 'gap-1.5',
            itemTrailingIcon: 'size-4',
            itemTrailingHighlightedIcon: 'size-4',
            itemTrailingKbds: 'gap-0.5',
            itemTrailingKbdsSize: 'sm'
          },
          md: {
            input: '[&>input]:h-12',
            empty: 'py-6 text-sm',
            label: 'p-1.5 text-xs gap-1.5',
            item: 'p-1.5 text-sm gap-1.5',
            itemLeadingIcon: 'size-5',
            itemLeadingAvatarSize: '2xs',
            itemLeadingChip: 'size-5',
            itemLeadingChipSize: 'md',
            itemTrailing: 'gap-1.5',
            itemTrailingIcon: 'size-5',
            itemTrailingHighlightedIcon: 'size-5',
            itemTrailingKbds: 'gap-0.5',
            itemTrailingKbdsSize: 'md'
          },
          lg: {
            input: '[&>input]:h-13',
            empty: 'py-7 text-sm',
            label: 'p-2 text-xs gap-2',
            item: 'p-2 text-sm gap-2',
            itemLeadingIcon: 'size-5',
            itemLeadingAvatarSize: '2xs',
            itemLeadingChip: 'size-5',
            itemLeadingChipSize: 'md',
            itemTrailing: 'gap-2',
            itemTrailingIcon: 'size-5',
            itemTrailingHighlightedIcon: 'size-5',
            itemTrailingKbds: 'gap-0.5',
            itemTrailingKbdsSize: 'md'
          },
          xl: {
            input: '[&>input]:h-14',
            empty: 'py-8 text-base',
            label: 'p-2 text-sm gap-2',
            item: 'p-2 text-base gap-2',
            itemLeadingIcon: 'size-6',
            itemLeadingAvatarSize: 'xs',
            itemLeadingChip: 'size-6',
            itemLeadingChipSize: 'lg',
            itemTrailing: 'gap-2',
            itemTrailingIcon: 'size-6',
            itemTrailingHighlightedIcon: 'size-6',
            itemTrailingKbds: 'gap-0.5',
            itemTrailingKbdsSize: 'lg'
          }
        },
        active: {
          true: {
            item: 'color-text-highlighted before:bg-background-elevated',
            itemLeadingIcon: 'color-text'
          },
          false: {
            item: [
              'color-text data-[highlighted]:not-data-[disabled]:color-text-highlighted data-[highlighted]:not-data-[disabled]:before:bg-background-elevated/50',
              'transition-colors before:transition-colors'
            ],
            itemLeadingIcon: [
              'color-text-dimmed [.group[data-highlighted]:not([data-disabled])_&]:color-text',
              'transition-colors'
            ]
          }
        },
        loading: {
          true: {
            itemLeadingIcon: 'animate-spin'
          }
        }
      },
      defaultVariants: {
        size: 'md'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from 'pohon-ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        commandPalette: {
          slots: {
            root: 'flex flex-col min-h-0 min-w-0 divide-y divide-border',
            input: '',
            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: 'text-center color-text-muted',
            label: 'font-600 color-text-highlighted',
            item: 'group outline-none flex w-full select-none items-start relative before:rounded-md before:content-empty before:inset-px before:absolute before:-z-1 data-[disabled]:opacity-75 data-[disabled]:cursor-not-allowed',
            itemLeadingIcon: 'shrink-0',
            itemLeadingAvatar: 'shrink-0',
            itemLeadingAvatarSize: '',
            itemLeadingChip: 'shrink-0',
            itemLeadingChipSize: '',
            itemTrailing: 'ms-auto inline-flex items-center',
            itemTrailingIcon: 'shrink-0',
            itemTrailingHighlightedIcon: 'color-text-dimmed shrink-0 hidden group-data-[highlighted]:inline-flex',
            itemTrailingKbds: 'hidden lg:inline-flex items-center shrink-0',
            itemTrailingKbdsSize: '',
            itemWrapper: 'flex-1 flex flex-col text-start min-w-0',
            itemLabel: 'truncate space-x-1 color-text-dimmed',
            itemLabelBase: 'color-text-highlighted [&>mark]:color-primary [&>mark]:bg-primary/15',
            itemLabelPrefix: 'color-text',
            itemLabelSuffix: 'color-text-dimmed [&>mark]:color-primary [&>mark]:bg-primary/15',
            itemDescription: 'truncate color-text-muted [&>mark]:color-primary [&>mark]:bg-primary/15'
          },
          variants: {
            virtualize: {
              true: {
                viewport: 'p-1 isolate'
              },
              false: {
                viewport: 'divide-y divide-border'
              }
            },
            size: {
              xs: {
                input: '[&>input]:h-10',
                empty: 'py-3 text-xs',
                label: 'p-1 text-[10px]/3 gap-1',
                item: 'p-1 text-xs gap-1',
                itemLeadingIcon: 'size-4',
                itemLeadingAvatarSize: '3xs',
                itemLeadingChip: 'size-4',
                itemLeadingChipSize: 'sm',
                itemTrailing: 'gap-1',
                itemTrailingIcon: 'size-4',
                itemTrailingHighlightedIcon: 'size-4',
                itemTrailingKbds: 'gap-0.5',
                itemTrailingKbdsSize: 'sm'
              },
              sm: {
                input: '[&>input]:h-11',
                empty: 'py-4 text-xs',
                label: 'p-1.5 text-[10px]/3 gap-1.5',
                item: 'p-1.5 text-xs gap-1.5',
                itemLeadingIcon: 'size-4',
                itemLeadingAvatarSize: '3xs',
                itemLeadingChip: 'size-4',
                itemLeadingChipSize: 'sm',
                itemTrailing: 'gap-1.5',
                itemTrailingIcon: 'size-4',
                itemTrailingHighlightedIcon: 'size-4',
                itemTrailingKbds: 'gap-0.5',
                itemTrailingKbdsSize: 'sm'
              },
              md: {
                input: '[&>input]:h-12',
                empty: 'py-6 text-sm',
                label: 'p-1.5 text-xs gap-1.5',
                item: 'p-1.5 text-sm gap-1.5',
                itemLeadingIcon: 'size-5',
                itemLeadingAvatarSize: '2xs',
                itemLeadingChip: 'size-5',
                itemLeadingChipSize: 'md',
                itemTrailing: 'gap-1.5',
                itemTrailingIcon: 'size-5',
                itemTrailingHighlightedIcon: 'size-5',
                itemTrailingKbds: 'gap-0.5',
                itemTrailingKbdsSize: 'md'
              },
              lg: {
                input: '[&>input]:h-13',
                empty: 'py-7 text-sm',
                label: 'p-2 text-xs gap-2',
                item: 'p-2 text-sm gap-2',
                itemLeadingIcon: 'size-5',
                itemLeadingAvatarSize: '2xs',
                itemLeadingChip: 'size-5',
                itemLeadingChipSize: 'md',
                itemTrailing: 'gap-2',
                itemTrailingIcon: 'size-5',
                itemTrailingHighlightedIcon: 'size-5',
                itemTrailingKbds: 'gap-0.5',
                itemTrailingKbdsSize: 'md'
              },
              xl: {
                input: '[&>input]:h-14',
                empty: 'py-8 text-base',
                label: 'p-2 text-sm gap-2',
                item: 'p-2 text-base gap-2',
                itemLeadingIcon: 'size-6',
                itemLeadingAvatarSize: 'xs',
                itemLeadingChip: 'size-6',
                itemLeadingChipSize: 'lg',
                itemTrailing: 'gap-2',
                itemTrailingIcon: 'size-6',
                itemTrailingHighlightedIcon: 'size-6',
                itemTrailingKbds: 'gap-0.5',
                itemTrailingKbdsSize: 'lg'
              }
            },
            active: {
              true: {
                item: 'color-text-highlighted before:bg-background-elevated',
                itemLeadingIcon: 'color-text'
              },
              false: {
                item: [
                  'color-text data-[highlighted]:not-data-[disabled]:color-text-highlighted data-[highlighted]:not-data-[disabled]:before:bg-background-elevated/50',
                  'transition-colors before:transition-colors'
                ],
                itemLeadingIcon: [
                  'color-text-dimmed [.group[data-highlighted]:not([data-disabled])_&]:color-text',
                  'transition-colors'
                ]
              }
            },
            loading: {
              true: {
                itemLeadingIcon: 'animate-spin'
              }
            }
          },
          defaultVariants: {
            size: 'md'
          }
        }
      }
    })
  ]
})

Changelog

No recent changes