0 Tk

SelectMenu

Display a select menu with advanced features.

Usage

The SelectMenu component renders by default a Select component and is based on the ui.select preset. You can use most of the Select props to configure the display if you don’t want to override the default slot such as color, variant, size, placeholder, icon, disabled, etc.

You can use the ui prop like the Select component to override the default config. The uiMenu prop can be used to override the default menu config.

Like the Select component, you can use the options prop to pass an array of strings or objects.

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" />
</template>

Multiple

You can use the multiple prop to select multiple values.

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref([])
</script>

<template>
  <USelectMenu 
    v-model="selected"
    :options="people"
    multiple
    placeholder="Select people"
  />
</template>

Objects

You can pass an array of objects to options and either compare on the whole object or use the by prop to compare on a specific key. You can configure which field will be used to display the label through the option-attribute prop that defaults to label. Additionally, you can use dot notation (e.g., user.name) to access nested object properties.

<script setup lang="ts">
import type { Avatar } from '#ui/types'

const people = [{
  id: 'benjamincanac',
  label: 'benjamincanac', 
  href: 'https://github.com/benjamincanac',
  target: '_blank',
  avatar: { src: 'https://avatars.githubusercontent.com/u/739984?v=4' }
}, {
  id: 'Atinux',
  label: 'Atinux',
  href: 'https://github.com/Atinux',
  target: '_blank', 
  avatar: { src: 'https://avatars.githubusercontent.com/u/904724?v=4' }
}, {
  id: 'smarroufin',
  label: 'smarroufin',
  href: 'https://github.com/smarroufin',
  target: '_blank',
  avatar: { src: 'https://avatars.githubusercontent.com/u/7547335?v=4' }
}, {
  id: 'nobody',
  label: 'Nobody',
  icon: 'i-heroicons-user-circle'
}]

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people">
    <template #leading>
      <UIcon v-if="selected.icon" :name="(selected.icon as string)" class="w-5 h-5" />
      <UAvatar v-else-if="selected.avatar" v-bind="(selected.avatar as Avatar)" size="2xs" />
    </template>
  </USelectMenu>
</template>

If you only want to select a single object property rather than the whole object as value, you can set the value-attribute property. This prop defaults to null.The value of the value-attribute field in options must be unique.

<script setup lang="ts">
const people = [{
  id: 1,
  name: 'Wade Cooper'
}, {
  id: 2,
  name: 'Arlene Mccoy'
}, {
  id: 3,
  name: 'Devon Webb'
}, {
  id: 4,
  name: 'Tom Cook'
}]

const selected = ref(people[0].id)
</script>

<template>
  <USelectMenu
    v-model="selected"
    :options="people"
    placeholder="Select people"
    value-attribute="id"
    option-attribute="name"
  />
</template>

Icon

Use the selected-icon prop to set a different icon or change it globally in ui.selectMenu.default.selectedIcon. Defaults to i-heroicons-check-20-solid.

<template>
  <USelectMenu
    selected-icon="i-heroicons-hand-thumb-up-solid"
    class="w-full lg:w-48"
    placeholder="Select a person"
    :options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
  />
</template>

Searchable

Use the searchable prop to enable search.

Use the searchable-placeholder prop to set a different placeholder or globally through the ui.selectMenu.default.searchablePlaceholder.label config. Defaults to Search....

This will use Headless UI Combobox component instead of Listbox.

<template>
  <USelectMenu
    searchable
    searchable-placeholder="Search a person..."
    class="w-full lg:w-48"
    placeholder="Select a person"
    :options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
  />
</template>

Attributes

Use the search-attributes prop with an array of property names to search on each option object. Nested attributes can be accessed using dot.notation. When the property value is an array or object, these are cast to string so these can be searched within.

<script setup lang="ts">
const options = [
  { id: 1, name: 'Wade Cooper', colors: ['red', 'yellow'] },
  { id: 2, name: 'Arlene Mccoy', colors: ['blue', 'yellow'] },
  { id: 3, name: 'Devon Webb', colors: ['green', 'blue'] },
  { id: 4, name: 'Tom Cook', colors: ['blue', 'red'] },
  { id: 5, name: 'Tanya Fox', colors: ['green', 'red'] },
  { id: 5, name: 'Hellen Schmidt', colors: ['green', 'yellow'] }
]

const selected = ref(options[1])
</script>

<template>
  <USelectMenu
    v-model="selected"
    :options="options"
    placeholder="Select a person"
    searchable
    searchable-placeholder="Search by name or color"
    option-attribute="name"
    by="id"
    :search-attributes="['name', 'colors']"
  >
    <template #option="{ option: person }">
      <span v-for="color in person.colors" :key="color.id" class="h-2 w-2 rounded-full" :class="`bg-${color}-500 dark:bg-${color}-400`" />
      <span class="truncate">{{ person.name }}</span>
    </template>
  </USelectMenu>
</template>

Clear on Close

By default, the search query will be kept after the menu is closed. To clear it on close, set the clear-search-on-close prop.

You can also configure this globally through the ui.selectMenu.default.clearSearchOnClose config. Defaults to false.

<template>
  <USelectMenu
    clear-search-on-close
    class="w-full lg:w-48"
    placeholder="Select a person"
    searchable
    searchable-placeholder="Search a person..."
    :options="['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']"
  />
</template>

Control the query

Use a v-model:query to control the search query.

Select a person

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref()
const query = ref('Wade')
</script>

<template>
  <USelectMenu
    v-model="selected"
    v-model:query="query"
    :options="people"
    placeholder="Select a person"
    searchable
  />
</template>

Async Search

Pass a function to the searchable prop to customize the search behavior and filter options according to your needs. The function will receive the query as its first argument and should return an array.

Use the debounce prop to adjust the delay of the function.

Use the searchableLazy prop to control the immediacy of data requests.

<script setup lang="ts">
const loading = ref(false)
const selected = ref([])

async function search(q: string) {
  loading.value = true

  const users: any[] = await $fetch('https://jsonplaceholder.typicode.com/users', { params: { q } })

  loading.value = false

  return users
}
</script>

<template>
  <USelectMenu
    v-model="selected"
    :loading="loading"
    :searchable="search"
    placeholder="Search for a user..."
  />
</template>

Creatable

Use the creatable prop to enable the creation of new options when the search doesn’t return any results (only works with searchable).

Try to search for something that doesn’t exist in the example below.

<script setup lang="ts">
const options = ref([
  { id: 1, name: 'bug', color: 'd73a4a' },
  { id: 2, name: 'documentation', color: '0075ca' },
  { id: 3, name: 'duplicate', color: 'cfd3d7' },
  { id: 4, name: 'enhancement', color: 'a2eeef' },
  { id: 5, name: 'good first issue', color: '7057ff' },
  { id: 6, name: 'help wanted', color: '008672' },
  { id: 7, name: 'invalid', color: 'e4e669' },
  { id: 8, name: 'question', color: 'd876e3' },
  { id: 9, name: 'wontfix', color: 'ffffff' }
])

const selected = ref([])

const labels = computed({
  get: () => selected.value,
  set: async (labels) => {
    const promises = labels.map(async (label) => {
      if (label.id) {
        return label
      }

      // In a real app, you would make an API call to create the label
      const response = {
        id: options.value.length + 1,
        name: label.name,
        color: generateColorFromString(label.name)
      }

      options.value.push(response)

      return response
    })

    selected.value = await Promise.all(promises)
  }
})

function hashCode(str) {
  let hash = 0
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash)
  }
  return hash
}

function intToRGB(i) {
  const c = (i & 0x00FFFFFF)
    .toString(16)
    .toUpperCase()

  return '00000'.substring(0, 6 - c.length) + c
}

function generateColorFromString(str) {
  return intToRGB(hashCode(str))
}
</script>

<template>
  <USelectMenu
    v-model="labels"
    by="id"
    name="labels"
    :options="options"
    option-attribute="name"
    multiple
    searchable
    creatable
  >
    <template #label>
      <template v-if="labels.length">
        <span class="flex items-center -space-x-1">
          <span v-for="label of labels" :key="label.id" class="flex-shrink-0 w-2 h-2 mt-px rounded-full" :style="{ background: `#${label.color}` }" />
        </span>
        <span>{{ labels.length }} label{{ labels.length > 1 ? 's' : '' }}</span>
      </template>
      <template v-else>
        <span class="text-gray-500 dark:text-gray-400 truncate">Select labels</span>
      </template>
    </template>

    <template #option="{ option }">
      <span
        class="flex-shrink-0 w-2 h-2 mt-px rounded-full"
        :style="{ background: `#${option.color}` }"
      />
      <span class="truncate">{{ option.name }}</span>
    </template>

    <template #option-create="{ option }">
      <span class="flex-shrink-0">New label:</span>
      <span
        class="flex-shrink-0 w-2 h-2 mt-px rounded-full -mx-1"
        :style="{ background: `#${generateColorFromString(option.name)}` }"
      />
      <span class="block truncate">{{ option.name }}</span>
    </template>
  </USelectMenu>
</template>

However, if you want to create options despite search query (apart from exact match), you can set the show-create-option-when prop to 'always'.

You can also configure this globally through the ui.selectMenu.default.showCreateOptionWhen config. Defaults to empty.

Try to search for something that exists in the example below, but not an exact match.

<script setup lang="ts">
const options = ref([
  { id: 1, name: 'bug' },
  { id: 2, name: 'documentation' },
  { id: 3, name: 'duplicate' },
  { id: 4, name: 'enhancement' },
  { id: 5, name: 'good first issue' },
  { id: 6, name: 'help wanted' },
  { id: 7, name: 'invalid' },
  { id: 8, name: 'question' },
  { id: 9, name: 'wontfix' }
])

const selected = ref([])

const labels = computed({
  get: () => selected.value,
  set: async (labels) => {
    const promises = labels.map(async (label) => {
      if (label.id) {
        return label
      }

      // In a real app, you would make an API call to create the label
      const response = {
        id: options.value.length + 1,
        name: label.name
      }

      options.value.push(response)

      return response
    })

    selected.value = await Promise.all(promises)
  }
})
</script>

<template>
  <USelectMenu
    v-model="labels"
    by="id"
    name="labels"
    :options="options"
    option-attribute="name"
    multiple
    searchable
    creatable
    show-create-option-when="always"
    placeholder="Select labels"
  />
</template>

Pass a function to the show-create-option-when prop to control wether or not to show the create option. This function takes two arguments: the query (as the first argument) and an array of current results (as the second argument). It should return true to display the create option.

The example below shows how to make the create option visible when the query is at least three characters long and does not exactly match any of the current results (case insensitive).

<script setup lang="ts">
const options = ref([
  { id: 1, name: 'bug' },
  { id: 2, name: 'documentation' },
  { id: 3, name: 'duplicate' },
  { id: 4, name: 'enhancement' },
  { id: 5, name: 'good first issue' },
  { id: 6, name: 'help wanted' },
  { id: 7, name: 'invalid' },
  { id: 8, name: 'question' },
  { id: 9, name: 'wontfix' }
])

const selected = ref([])

const labels = computed({
  get: () => selected.value,
  set: async (labels) => {
    const promises = labels.map(async (label) => {
      if (label.id) {
        return label
      }

      // In a real app, you would make an API call to create the label
      const response = {
        id: options.value.length + 1,
        name: label.name
      }

      options.value.push(response)

      return response
    })

    selected.value = await Promise.all(promises)
  }
})

const showCreateOption = (query, results) => {
  const lowercaseQuery = String.prototype.toLowerCase.apply(query || '')
  return lowercaseQuery.length >= 3 && !results.find((option) => {
    return String.prototype.toLowerCase.apply(option['name'] || '') === lowercaseQuery
  })
}
</script>

<template>
  <USelectMenu
    v-model="labels"
    by="id"
    name="labels"
    :options="options"
    option-attribute="name"
    multiple
    searchable
    creatable
    :show-create-option-when="showCreateOption"
    placeholder="Select labels"
  />
</template>

Popper

Use the popper prop to customize the popper instance.

Arrow

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" :popper="{ arrow: true }" />
</template>

Placement

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" :popper="{ placement: 'left-end' }" />
</template>

Offset

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" :popper="{ offsetDistance: 0 }" />
</template>

Slots

label

You can override the #label slot and handle the display yourself.

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref([])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" multiple>
    <template #label>
      <span v-if="selected.length" class="truncate">{{ selected.join(', ') }}</span>
      <span v-else>Select people</span>
    </template>
  </USelectMenu>
</template>

default

You can also override the #default slot entirely.

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[3])
</script>

<template>
  <USelectMenu v-slot="{ open }" v-model="selected" :options="people">
    <UButton color="gray" class="flex-1 justify-between">
      {{ selected }}

      <UIcon name="i-heroicons-chevron-right-20-solid" class="w-5 h-5 transition-transform text-gray-400 dark:text-gray-500" :class="[open && 'transform rotate-90']" />
    </UButton>
  </USelectMenu>
</template>

option

Use the #option slot to customize the option content. You will have access to the optionactive and selected properties in the slot scope.

<script setup lang="ts">
const people = [
  { name: 'Wade Cooper', online: true },
  { name: 'Arlene Mccoy', online: false },
  { name: 'Devon Webb', online: false },
  { name: 'Tom Cook', online: true },
  { name: 'Tanya Fox', online: false },
  { name: 'Hellen Schmidt', online: true },
  { name: 'Caroline Schultz', online: true },
  { name: 'Mason Heaney', online: false },
  { name: 'Claudie Smitham', online: true },
  { name: 'Emil Schaefer', online: false }
]

const selected = ref(people[3])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" option-attribute="name">
    <template #label>
      <span :class="[selected.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
      <span class="truncate">{{ selected.name }}</span>
    </template>

    <template #option="{ option: person }">
      <span :class="[person.online ? 'bg-green-400' : 'bg-gray-200', 'inline-block h-2 w-2 flex-shrink-0 rounded-full']" aria-hidden="true" />
      <span class="truncate">{{ person.name }}</span>
    </template>
  </USelectMenu>
</template>

option-empty

Use the #option-empty slot to customize the content displayed when the searchable prop is true and there is no options. You will have access to the query property in the slot scope.

You can also configure this globally through the ui.selectMenu.default.optionEmpty.label config. The token {query} will be replaced by query property. Defaults to No results for "{query}"..

<script setup lang="ts">
const people = ['Wade Cooper', 'Arlene Mccoy', 'Devon Webb', 'Tom Cook', 'Tanya Fox', 'Hellen Schmidt', 'Caroline Schultz', 'Mason Heaney', 'Claudie Smitham', 'Emil Schaefer']

const selected = ref(people[0])
</script>

<template>
  <USelectMenu v-model="selected" :options="people" searchable>
    <template #option-empty="{ query }">
      <q>{{ query }}</q> not found
    </template>
  </USelectMenu>
</template>

option-create

Use the #option-create slot to customize the content displayed when the creatable prop is true and there is no options. You will have access to the query property in the slot scope.

An example is available in the Creatable section.

empty

Use the #empty slot to customize the content displayed when there is no options.

You can also configure this globally through the ui.selectMenu.default.empty.label config. Defaults to No options..

<script setup lang="ts">
const people = []

const selected = ref()
</script>

<template>
  <USelectMenu v-model="selected" :options="people">
    <template #empty>
      No people
    </template>
  </USelectMenu>
</template>

Props

Here are all the available props for the SelectMenu component:

Prop Type Default
v-model any undefined
options array []
placeholder string undefined
multiple boolean false
searchable boolean | Function false
searchablePlaceholder string 'Search...'
searchAttributes string[] undefined
clearSearchOnClose boolean false
creatable boolean false
showCreateOptionWhen 'empty' | 'always' | Function
by string undefined
optionAttribute string 'label'
valueAttribute string undefined
selectedIcon string 'i-heroicons-check-20-solid'
popper object undefined
ui object undefined
uiMenu object {}
selectClass string null
searchableLazy boolean false
required boolean false
disabled boolean false
leading boolean false
trailing boolean false
loading boolean false

The component also inherits all props from the USelect component.

* Popper definition:

Prop Type
locked boolean
overflowPadding number
offsetDistance number
offsetSkid number
placement `‘left’
gpuAcceleration boolean
adaptive boolean
scroll boolean
resize boolean
arrow boolean

Config

Use the ui prop to override the select config and the uiMenu prop to override the menu config.

Select (ui)

{
  wrapper: 'relative',
  base: 'relative block w-full disabled:cursor-not-allowed disabled:opacity-75 focus:outline-none border-0',
  form: 'form-select',
  rounded: 'rounded-md',
  placeholder: 'text-gray-400 dark:text-gray-500',
  file: {
    base: 'file:mr-1.5 file:font-medium file:text-gray-500 dark:file:text-gray-400 file:bg-transparent file:border-0 file:p-0 file:outline-none'
  },
  size: {
    '2xs': 'text-xs',
    xs: 'text-xs',
    sm: 'text-sm',
    md: 'text-sm',
    lg: 'text-sm',
    xl: 'text-base'
  },
  gap: {
    '2xs': 'gap-x-1',
    xs: 'gap-x-1.5',
    sm: 'gap-x-1.5',
    md: 'gap-x-2',
    lg: 'gap-x-2.5',
    xl: 'gap-x-2.5'
  },
  padding: {
    '2xs': 'px-2 py-1',
    xs: 'px-2.5 py-1.5',
    sm: 'px-2.5 py-1.5',
    md: 'px-3 py-2',
    lg: 'px-3.5 py-2.5',
    xl: 'px-3.5 py-2.5'
  },
  leading: {
    padding: {
      '2xs': 'ps-7',
      xs: 'ps-8',
      sm: 'ps-9',
      md: 'ps-10',
      lg: 'ps-11',
      xl: 'ps-12'
    }
  },
  trailing: {
    padding: {
      '2xs': 'pe-7',
      xs: 'pe-8',
      sm: 'pe-9',
      md: 'pe-10',
      lg: 'pe-11',
      xl: 'pe-12'
    }
  },
  color: {
    white: {
      outline: 'shadow-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400'
    },
    gray: {
      outline: 'shadow-sm bg-gray-50 dark:bg-gray-800 text-gray-900 dark:text-white ring-1 ring-inset ring-gray-300 dark:ring-gray-700 focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400'
    }
  },
  variant: {
    outline: 'shadow-sm bg-transparent text-gray-900 dark:text-white ring-1 ring-inset ring-{color}-500 dark:ring-{color}-400 focus:ring-2 focus:ring-{color}-500 dark:focus:ring-{color}-400',
    none: 'bg-transparent focus:ring-0 focus:shadow-none'
  },
  icon: {
    base: 'flex-shrink-0 text-gray-400 dark:text-gray-500',
    color: 'text-{color}-500 dark:text-{color}-400',
    loading: 'animate-spin',
    size: {
      '2xs': 'h-4 w-4',
      xs: 'h-4 w-4',
      sm: 'h-5 w-5',
      md: 'h-5 w-5',
      lg: 'h-5 w-5',
      xl: 'h-6 w-6'
    },
    leading: {
      wrapper: 'absolute inset-y-0 start-0 flex items-center',
      pointer: 'pointer-events-none',
      padding: {
        '2xs': 'px-2',
        xs: 'px-2.5',
        sm: 'px-2.5',
        md: 'px-3',
        lg: 'px-3.5',
        xl: 'px-3.5'
      }
    },
    trailing: {
      wrapper: 'absolute inset-y-0 end-0 flex items-center',
      pointer: 'pointer-events-none',
      padding: {
        '2xs': 'px-2',
        xs: 'px-2.5',
        sm: 'px-2.5',
        md: 'px-3',
        lg: 'px-3.5',
        xl: 'px-3.5'
      }
    }
  },
  default: {
    size: 'sm',
    color: 'white',
    variant: 'outline',
    loadingIcon: 'i-heroicons-arrow-path-20-solid',
    trailingIcon: 'i-heroicons-chevron-down-20-solid'
  }
}

SelectMenu (uiMenu)

{
  container: 'z-20 group',
  trigger: 'flex items-center w-full',
  width: 'w-full',
  height: 'max-h-60',
  base: 'relative focus:outline-none overflow-y-auto scroll-py-1',
  background: 'bg-white dark:bg-gray-800',
  shadow: 'shadow-lg',
  rounded: 'rounded-md',
  padding: 'p-1',
  ring: 'ring-1 ring-gray-200 dark:ring-gray-700',
  empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
  option: {
    base: 'cursor-default select-none relative flex items-center justify-between gap-1',
    rounded: 'rounded-md',
    padding: 'px-1.5 py-1.5',
    size: 'text-sm',
    color: 'text-gray-900 dark:text-white',
    container: 'flex items-center gap-1.5 min-w-0',
    active: 'bg-gray-100 dark:bg-gray-900',
    inactive: '',
    selected: 'pe-7',
    disabled: 'cursor-not-allowed opacity-50',
    empty: 'text-sm text-gray-400 dark:text-gray-500 px-2 py-1.5',
    icon: {
      base: 'flex-shrink-0 h-5 w-5',
      active: 'text-gray-900 dark:text-white',
      inactive: 'text-gray-400 dark:text-gray-500'
    },
    selectedIcon: {
      wrapper: 'absolute inset-y-0 end-0 flex items-center',
      padding: 'pe-2',
      base: 'h-5 w-5 text-gray-900 dark:text-white flex-shrink-0'
    },
    avatar: {
      base: 'flex-shrink-0',
      size: '2xs'
    },
    chip: {
      base: 'flex-shrink-0 w-2 h-2 mx-1 rounded-full'
    },
    create: 'block truncate'
  },
  transition: {
    leaveActiveClass: 'transition ease-in duration-100',
    leaveFromClass: 'opacity-100',
    leaveToClass: 'opacity-0'
  },
  popper: {
    placement: 'bottom-end'
  },
  default: {
    selectedIcon: 'i-heroicons-check-20-solid',
    clearSearchOnClose: false,
    showCreateOptionWhen: 'empty',
    searchablePlaceholder: {
      label: 'Search...'
    },
    empty: {
      label: 'No options.'
    },
    optionEmpty: {
      label: 'No results for "{query}".'
    }
  },
  arrow: {
    base: 'invisible before:visible before:block before:rotate-45 before:z-[-1] before:w-2 before:h-2',
    ring: 'before:ring-1 before:ring-gray-200 dark:before:ring-gray-700',
    rounded: 'before:rounded-sm',
    background: 'before:bg-white dark:before:bg-gray-700',
    shadow: 'before:shadow',
    placement: "group-data-[popper-placement*='right']:-left-1 group-data-[popper-placement*='left']:-right-1 group-data-[popper-placement*='top']:-bottom-1 group-data-[popper-placement*='bottom']:-top-1"
  },
  select: 'inline-flex items-center text-left cursor-default',
  input: 'block w-[calc(100%+0.5rem)] focus:ring-transparent text-sm px-3 py-1.5 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 border-0 border-b border-gray-200 dark:border-gray-700 sticky -top-1 -mt-1 mb-1 -mx-1 z-10 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none',
  required: 'absolute inset-0 w-px opacity-0 cursor-default',
  label: 'block truncate'
}