Examples
Basic
| Prop | Default | Type | Description | 
|---|---|---|---|
| items | - | T[]|NComboboxGroupProps<ExtractItemType<T>>[] | The items to display in the combobox. | 
| modelValue | - | AcceptableValue|AcceptableValue[] | The controlled value of the listbox. Can be binded with with v-model. | 
| disabled | - | boolean | When true, prevents the user from interacting with the combobox. | 
| open | - | boolean | The controlled open state of the combobox. Can be binded with v-model. | 
| label | - | string | The heading to display for the grouped item. | 
| labelKey | label | string | The key name to use to display in the select items. | 
| valueKey | value | string | The key name to use to display in the selected value. | 
| textEmpty | No items found. | string | The text to display when the combobox is empty. | 
| by | - | string,((a: AcceptableValue, b: AcceptableValue) => boolean) | Use this to compare objects by a particular field, or pass your own comparison function for complete control over how objects are compared. | 
The T generic extends AcceptableValue from Reka UI. When using grouped items, the item type is automatically extracted.
 Preview
 Code
<script setup lang="ts">
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFramework = ref<typeof frameworks[number]>()
</script>
<template>
  <div class="flex">
    <NCombobox
      v-model="selectedFramework"
      :items="frameworks"
      :_combobox-input="{
        placeholder: 'Select framework...',
        autocomplete: 'off',
      }"
      by="value"
      text-empty="No frameworks found."
    />
  </div>
</template>
 Read more in Reka Combobox Root API.
Multiple
Allow users to select multiple items from the list.
| Prop | Default | Type | Description | 
|---|---|---|---|
| multiple | false | boolean | When true, allows the user to select multiple items. | 
 Preview
 Code
<script setup lang="ts">
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFramework = ref<typeof frameworks[number][]>()
</script>
<template>
  <div class="flex">
    <NCombobox
      v-model="selectedFramework"
      multiple
      :items="frameworks"
      :_combobox-input="{
        placeholder: 'Select framework...',
        autocomplete: 'off',
      }"
      by="value"
    />
  </div>
</template>
Trigger
Add a custom trigger content.
| Prop | Default | Type | Description | 
|---|---|---|---|
| _comboboxTrigger | { btn: 'solid-white', trailing: 'i-lucide-chevrons-up-down' } | NComboboxTriggerProps | The button props for the trigger, you can refer to the Button component for more details. | 
 Preview
 Code
<script setup lang="ts">
const users = [
  { id: '1', username: 'shadcn' },
  { id: '2', username: 'leerob' },
  { id: '3', username: 'evilrabbit' },
]
const selectedUser = ref<typeof users[number]>()
</script>
<template>
  <div class="flex">
    <NCombobox
      v-model="selectedUser"
      :items="users"
      by="username"
      :_combobox-input="{
        placeholder: 'Select user...',
      }"
    >
      <template #trigger="{ modelValue }">
        <template v-if="modelValue">
          <div class="flex items-center gap-2">
            <NAvatar
              :src="`https://github.com/${modelValue.username}.png`"
              :alt="modelValue.username"
              square="5"
            />
            {{ modelValue.username }}
          </div>
        </template>
        <template v-else>
          Select user...
        </template>
      </template>
      <template #label="{ item }">
        <NAvatar
          square="5"
          :src="`https://github.com/${item.username}.png`"
          :alt="item.username"
        />
        {{ item.username }}
      </template>
      <template #footer>
        <NComboboxSeparator />
        <NComboboxGroup>
          <NComboboxItem :value="null">
            <NIcon name="i-lucide-plus-circle" />
            Create user
          </NComboboxItem>
        </NComboboxGroup>
      </template>
    </NCombobox>
  </div>
</template>
 Read more in Button component
 Read more in Reka Combobox Trigger API
List / Content
| Prop | Default | Type | Description | 
|---|---|---|---|
| _comboboxList | { align: 'center', sideOffset: 4, position: 'popper' } | NComboboxListProps | Props for customizing the dropdown list of the combobox. Controls alignment, offset distance from trigger, positioning behavior and more. | 
 Preview
 Code
Create a new project
<script setup lang="ts">
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFramework = ref<typeof frameworks[number]>()
</script>
<template>
  <div className="flex w-full flex-col items-start justify-between rounded-md border px-4 py-3 sm:flex-row sm:items-center">
    <NCombobox
      v-model="selectedFramework"
      :items="frameworks"
      :_combobox-input="{
        placeholder: 'Select framework...',
        autocomplete: 'off',
      }"
      :_combobox-list="{
        align: 'start',
        side: 'right',
      }"
      by="value"
      text-empty="No frameworks found."
    >
      <template #trigger>
        <template v-if="selectedFramework">
          {{ selectedFramework.label }}
        </template>
        <template v-else>
          Select framework...
        </template>
      </template>
    </NCombobox>
    <p className="text-sm font-medium leading-none">
      <span v-if="selectedFramework" className="mr-2 rounded-lg bg-primary px-2 py-1 text-xs text-inverted">
        {{ selectedFramework.label }}
      </span>
      <span className="text-muted">Create a new project</span>
    </p>
  </div>
</template>
 Read more in Reka Combobox Content API
Group
| Props | Default | Type | Description | 
|---|
 Preview
 Code
<script setup lang="ts">
const timezones = [
  {
    label: 'Americas',
    items: [
      { value: 'America/New_York', label: '(GMT-5) New York' },
      { value: 'America/Los_Angeles', label: '(GMT-8) Los Angeles' },
      { value: 'America/Chicago', label: '(GMT-6) Chicago' },
      { value: 'America/Toronto', label: '(GMT-5) Toronto' },
      { value: 'America/Vancouver', label: '(GMT-8) Vancouver' },
      { value: 'America/Sao_Paulo', label: '(GMT-3) São Paulo' },
    ],
  },
  {
    label: 'Europe',
    items: [
      { value: 'Europe/London', label: '(GMT+0) London' },
      { value: 'Europe/Paris', label: '(GMT+1) Paris' },
      { value: 'Europe/Berlin', label: '(GMT+1) Berlin' },
      { value: 'Europe/Rome', label: '(GMT+1) Rome' },
      { value: 'Europe/Madrid', label: '(GMT+1) Madrid' },
      { value: 'Europe/Amsterdam', label: '(GMT+1) Amsterdam' },
    ],
  },
  {
    label: 'Asia/Pacific',
    items: [
      { value: 'Asia/Tokyo', label: '(GMT+9) Tokyo' },
      { value: 'Asia/Shanghai', label: '(GMT+8) Shanghai' },
      { value: 'Asia/Singapore', label: '(GMT+8) Singapore' },
      { value: 'Asia/Dubai', label: '(GMT+4) Dubai' },
      { value: 'Australia/Sydney', label: '(GMT+11) Sydney' },
      { value: 'Asia/Seoul', label: '(GMT+9) Seoul' },
    ],
  },
]
type Timezone = typeof timezones[0]
const selectedTimezone = ref<Timezone['items'][number]>(timezones[0].items[0])
const selectedGroup = computed(() => timezones.find(group => group.items.find(tz => tz.value === selectedTimezone.value?.value)))
</script>
<template>
  <NCombobox
    v-model="selectedTimezone"
    :items="timezones"
    by="value"
    :_combobox-input="{
      placeholder: 'Select timezone...',
    }"
    :_combobox-list="{
      class: 'w-300px',
      align: 'start',
    }"
    :_combobox-viewport="{
      class: 'max-h-260px',
    }"
    :_combobox-trigger="{
      class: 'h-12 px-2.5',
    }"
    class="flex"
  >
    <template #trigger>
      <template v-if="selectedTimezone">
        <div class="flex flex-col items-start gap-0.5">
          <span class="text-xs font-normal opacity-75">
            {{ selectedGroup?.label }}
          </span>
          <span>{{ selectedTimezone.label }}</span>
        </div>
      </template>
      <template v-else>
        Select timezone...
      </template>
    </template>
  </NCombobox>
</template>
 Read more in Reka Combobox Group Items API
Form Field
Use the NFormField component to create a form field.
 Preview
 Code
<script setup lang="ts">
import { toTypedSchema } from '@vee-validate/zod'
import * as z from 'zod'
const formSchema = toTypedSchema(z.object({
  framework: z.object({
    value: z.string().min(1, 'This field is required'),
    label: z.string().min(1, 'This field is required'),
  }),
}))
useForm({
  validationSchema: formSchema,
  validateOnMount: true,
})
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFramework = ref<typeof frameworks[number]>()
</script>
<template>
  <form class="flex">
    <NFormField
      name="framework"
      label="Framework"
      description="Select a framework without a trigger"
    >
      <NCombobox
        v-model="selectedFramework"
        :items="frameworks"
        :_combobox-input="{
          placeholder: 'Select framework...',
          autocomplete: 'off',
        }"
        by="value"
      />
    </NFormField>
  </form>
</template>
 Read more in Form component
Size
Adjust the combobox size without limits. Use breakpoints (e.g., sm:sm, xs:lg) for responsive sizes or states (e.g., hover:lg, focus:3xl) for state-based sizes.
| Prop | Default | Type | Description | 
|---|---|---|---|
| size | sm | string | Adjusts the overall size of the combobox component. | 
| _comboboxInput.size | sm | string | Customizes the size of the combobox input element. | 
| _comboboxItem.size | sm | string | Customizes the size of each item within the combobox dropdown. | 
| _comboboxTrigger.size | sm | string | Modifies the size of the combobox trigger element. | 
 Preview
 Code
<script setup lang="ts">
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFramework = ref<typeof frameworks[number]>()
</script>
<template>
  <div class="flex flex-wrap items-center gap-4">
    <NCombobox
      v-model="selectedFramework"
      size="xs"
      :items="frameworks"
      :_combobox-input="{
        placeholder: 'Select framework...',
        autocomplete: 'off',
      }"
      class="flex"
      by="value"
    >
      <template #trigger="{ modelValue }">
        <template v-if="modelValue">
          {{ modelValue }}
        </template>
        <template v-else>
          Select Framework
        </template>
      </template>
    </NCombobox>
    <NCombobox
      v-model="selectedFramework"
      size="sm"
      :items="frameworks"
      :_combobox-input="{
        placeholder: 'Select framework...',
        autocomplete: 'off',
      }"
      class="flex"
      by="value"
    >
      <template #trigger="{ modelValue }">
        <template v-if="modelValue">
          {{ modelValue }}
        </template>
        <template v-else>
          Select Framework
        </template>
      </template>
    </NCombobox>
    <NCombobox
      v-model="selectedFramework"
      size="md"
      :items="frameworks"
      :_combobox-input="{
        placeholder: 'Select framework...',
        autocomplete: 'off',
      }"
      class="flex"
      by="value"
    >
      <template #trigger="{ modelValue }">
        <template v-if="modelValue">
          {{ modelValue }}
        </template>
        <template v-else>
          Select Framework
        </template>
      </template>
    </NCombobox>
  </div>
</template>
Slots
| Name | Props | Description | 
|---|---|---|
| default | - | Slot for advanced custom rendering using sub-components. | 
| trigger | modelValue,open | Custom content inside the default trigger button. | 
| trigger-wrapper | modelValue,open | Completely replace the default trigger button/component. | 
| input-wrapper | modelValue,open | Completely replace the default input component. | 
| item | item,selected | Custom rendering for the entire content of each combobox item. | 
| label | item | Custom rendering for the label text within each item. | 
| indicator | item | Custom rendering for the selection indicator within each item. | 
| header | - | Content rendered inside the list, before the items. | 
| body | - | Completely replace the default item list container ( Viewport). | 
| footer | - | Content rendered inside the list, after the items. | 
Custom Rendering
Use the default slot for full control over the combobox's structure. This allows you to compose the combobox using its individual sub-components (like ComboboxInput, ComboboxList, etc., listed in the Components section), similar to libraries like shadcn/ui.
 Preview
 Code
<script setup lang="ts">
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFramework = ref<typeof frameworks[number]>()
</script>
<template>
  <NCombobox
    v-model="selectedFramework"
    by="label"
  >
    <NComboboxAnchor as-child>
      <NComboboxTrigger class="w-[200px]">
        {{ selectedFramework?.label ?? 'Select framework...' }}
      </NComboboxTrigger>
    </NComboboxAnchor>
    <NComboboxList>
      <NComboboxInput
        class="border-0 border-b-1 rounded-none placeholder:text-gray-500 focus-visible:ring-0"
        placeholder="Select framework..."
      />
      <NComboboxEmpty>
        No framework found.
      </NComboboxEmpty>
      <NComboboxGroup>
        <NComboboxItem
          v-for="framework in frameworks"
          :key="framework.value"
          :value="framework"
        >
          {{ framework.label }}
          <NComboboxItemIndicator>
            <NIcon name="i-lucide-check" />
          </NComboboxItemIndicator>
        </NComboboxItem>
      </NComboboxGroup>
    </NComboboxList>
  </NCombobox>
</template>
Custom Multiple Selection
Customize the multiple selection content.
 Preview
 Code
<script setup lang="ts">
const frameworks = [
  { value: 'next.js', label: 'Next.js' },
  { value: 'sveltekit', label: 'SvelteKit' },
  { value: 'nuxt', label: 'Nuxt' },
  { value: 'remix', label: 'Remix' },
  { value: 'astro', label: 'Astro' },
]
const selectedFrameworks = ref<typeof frameworks[number][]>([])
</script>
<template>
  <div class="flex">
    <NCombobox
      v-model="selectedFrameworks"
      :items="frameworks"
      by="value"
      multiple
      :_combobox-input="{
        placeholder: 'Select frameworks...',
      }"
      :_combobox-list="{
        class: 'w-300px',
        align: 'start',
      }"
    >
      <template #trigger="{ modelValue }">
        {{ modelValue?.length
          ? modelValue.map(val => {
            const framework = frameworks.find(f => f.value === val.value)
            return framework ? framework.label : val
          }).join(", ")
          : "Select frameworks..." }}
      </template>
      <template #item="{ item, selected }">
        <NCheckbox
          :model-value="selected"
          tabindex="-1"
          aria-hidden="true"
        />
        {{ item.label }}
      </template>
    </NCombobox>
  </div>
</template>
Presets
 shortcuts/combobox.ts 
type ComboboxPrefix = 'combobox'
export const staticCombobox: Record<`${ComboboxPrefix}-${string}` | ComboboxPrefix, string> = {
// base
  'combobox': 'flex',
  'combobox-trigger-info-icon': 'i-info',
  'combobox-trigger-error-icon': 'i-error',
  'combobox-trigger-success-icon': 'i-success',
  'combobox-trigger-warning-icon': 'i-warning',
  'combobox-trigger-trailing-icon': 'i-lucide-chevrons-up-down',
  'combobox-input-leading-icon': 'i-lucide-search',
  'combobox-trigger': 'px-0.8571428571428571em min-w-200px w-full justify-between font-normal [&>span]:truncate',
  'combobox-trigger-trailing': 'size-1.1428571428571428em data-[status=error]:text-error data-[status=success]:text-success data-[status=warning]:text-warning data-[status=info]:text-info data-[status=default]:(n-disabled) rtl:mr-auto ltr:ml-auto',
  'combobox-trigger-leading': 'size-1.1428571428571428em',
  'combobox-item': 'data-[highlighted]:bg-accent data-[highlighted]:text-accent relative flex cursor-default items-center gap-2 rounded-sm px-0.5em py-0.375em text-1em outline-none select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50',
  'combobox-item-indicator': 'ml-auto',
  'combobox-item-indicator-icon': '',
  'combobox-item-indicator-icon-name': 'i-check',
  'combobox-anchor': 'w-full',
  'combobox-empty': 'py-1.7142857142857142em text-center text-1em leading-1.4285714285714286em',
  'combobox-group': 'overflow-hidden p-0.2857142857142857em text-foreground',
  'combobox-label': 'px-0.6666666666666666em py-0.5em text-0.8571428571428571em leading-1.1428571428571428em text-muted font-medium px-2',
  'combobox-list': 'z-50 w-[--reka-popper-anchor-width] rounded-md border bg-popover text-popover overflow-hidden shadow-md outline-none  data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
  'combobox-separator': 'bg-border -mx-1 h-px',
  'combobox-viewport': 'max-h-300px scroll-py-1 overflow-x-hidden overflow-y-auto',
}
export const dynamicCombobox: [RegExp, (params: RegExpExecArray) => string][] = [
// dynamic preset
]
export const combobox = [
  ...dynamicCombobox,
  staticCombobox,
]
Props
 types/combobox.ts 
import type { AcceptableValue, ComboboxAnchorProps, ComboboxContentProps, ComboboxEmptyProps, ComboboxGroupProps, ComboboxInputProps, ComboboxItemIndicatorProps, ComboboxItemProps, ComboboxLabelProps, ComboboxPortalProps, ComboboxRootProps, ComboboxSeparatorProps, ComboboxTriggerProps, ComboboxViewportProps } from 'reka-ui'
import type { HTMLAttributes } from 'vue'
import type { NButtonProps } from './button'
import type { NCheckboxProps } from './checkbox'
import type { NInputProps } from './input'
interface BaseExtensions {
  class?: HTMLAttributes['class']
  size?: HTMLAttributes['class']
}
// Extract the actual item type when dealing with grouped items
export type ExtractItemType<T> = T extends { items: infer I extends AcceptableValue[] } ? I[number] : T
export interface NComboboxProps<T extends AcceptableValue, M extends boolean> extends Omit<ComboboxRootProps<ExtractItemType<T>>, 'modelValue'>, Pick<NComboboxInputProps, 'status' | 'id'>, BaseExtensions {
  /**
   * The model value for the combobox.
   * When using grouped items, this will be the item type from within the groups.
   */
  modelValue?: (M extends true ? ExtractItemType<T>[] : ExtractItemType<T>) | null
  /**
   * The items to display in the combobox.
   *
   * @default []
   */
  items?: T[] | NComboboxGroupProps<ExtractItemType<T>>[]
  /**
   * The key name to use to display in the select items.
   *
   * @default 'label'
   */
  labelKey?: keyof ExtractItemType<T>
  /**
   * The key name to use to display in the selected value.
   *
   * @default 'value'
   */
  valueKey?: keyof ExtractItemType<T>
  /**
   * Whether to show a separator between groups.
   *
   * @default false
   */
  groupSeparator?: boolean
  /**
   * The text to display when the combobox is empty.
   *
   * @default 'No items found.'
   */
  textEmpty?: string
  /**
   * The heading to display for the grouped item.
   *
   * @default ''
   */
  label?: string
  multiple?: M
  /**
   * Sub-component configurations
   */
  _comboboxAnchor?: NComboboxAnchorProps
  _comboboxEmpty?: NComboboxEmptyProps
  _comboboxGroup?: NComboboxGroupProps<ExtractItemType<T>>
  _comboboxInput?: NComboboxInputProps
  _comboboxItem?: NComboboxItemProps<ExtractItemType<T>>
  _comboboxItemIndicator?: NComboboxItemIndicatorProps
  _comboboxLabel?: NComboboxLabelProps
  _comboboxList?: NComboboxListProps
  _comboboxSeparator?: NComboboxSeparatorProps
  _comboboxTrigger?: NComboboxTriggerProps
  _comboboxViewport?: NComboboxViewportProps
  _comboboxCheckbox?: NCheckboxProps
  _comboboxContent?: ComboboxContentProps
  _comboboxPortal?: ComboboxPortalProps
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/combobox.ts
   */
  una?: NComboboxUnaProps
}
export interface NComboboxLabelProps extends ComboboxLabelProps, BaseExtensions {
  label?: string
  una?: Pick<NComboboxUnaProps, 'comboboxLabel'>
}
export interface NComboboxItemProps<T> extends ComboboxItemProps<T>, BaseExtensions {
  una?: Pick<NComboboxUnaProps, 'comboboxItem'>
}
export interface NComboboxAnchorProps extends ComboboxAnchorProps, BaseExtensions {
  una?: Pick<NComboboxUnaProps, 'comboboxAnchor'>
}
export interface NComboboxEmptyProps extends ComboboxEmptyProps, BaseExtensions {
  una?: Pick<NComboboxUnaProps, 'comboboxEmpty'>
}
export interface NComboboxGroupProps<T extends AcceptableValue> extends ComboboxGroupProps, BaseExtensions {
  label?: string
  items?: T[]
  _comboboxItem?: Partial<NComboboxItemProps<T>>
  _comboboxLabel?: Partial<NComboboxLabelProps>
  una?: Pick<NComboboxUnaProps, 'comboboxGroup' | 'comboboxLabel'>
}
export interface NComboboxInputProps extends ComboboxInputProps, Omit<NInputProps, 'modelValue'> {
  [key: string]: any
}
export interface NComboboxItemIndicatorProps extends ComboboxItemIndicatorProps, BaseExtensions {
  icon?: HTMLAttributes['class']
  una?: Pick<NComboboxUnaProps, 'comboboxItemIndicator' | 'comboboxItemIndicatorIcon'>
}
export interface NComboboxListProps extends ComboboxContentProps, BaseExtensions {
  viewportClass?: HTMLAttributes['class']
  una?: Pick<NComboboxUnaProps, 'comboboxList'>
  _comboboxPortal?: ComboboxPortalProps
}
export interface NComboboxSeparatorProps extends ComboboxSeparatorProps, BaseExtensions {
  una?: Pick<NComboboxUnaProps, 'comboboxSeparator'>
}
export interface NComboboxTriggerProps extends ComboboxTriggerProps, NButtonProps {
  /**
   * The unique id of the select trigger to be used for the form field.
   */
  id?: string
  /**
   * The status of the select input.
   */
  status?: 'info' | 'success' | 'warning' | 'error'
  /**
   * `UnaUI` preset configuration
   *
   * @see https://github.com/una-ui/una-ui/blob/main/packages/preset/src/_shortcuts/combobox.ts
   */
  una?: Pick<NComboboxUnaProps, 'comboboxTrigger' | 'comboboxTriggerLeading' | 'comboboxTriggerTrailing' | 'comboboxTriggerInfoIcon' | 'comboboxTriggerSuccessIcon' | 'comboboxTriggerWarningIcon' | 'comboboxTriggerErrorIcon'> & NButtonProps['una']
}
export interface NComboboxViewportProps extends ComboboxViewportProps, BaseExtensions {
  una?: Pick<NComboboxUnaProps, 'comboboxViewport'>
}
export interface NComboboxUnaProps {
  combobox?: HTMLAttributes['class']
  comboboxAnchor?: HTMLAttributes['class']
  comboboxLabel?: HTMLAttributes['class']
  comboboxItem?: HTMLAttributes['class']
  comboboxItemIndicator?: HTMLAttributes['class']
  comboboxItemIndicatorIcon?: HTMLAttributes['class']
  comboboxSeparator?: HTMLAttributes['class']
  comboboxViewport?: HTMLAttributes['class']
  comboboxEmpty?: HTMLAttributes['class']
  comboboxGroup?: HTMLAttributes['class']
  comboboxList?: HTMLAttributes['class']
  comboboxTrigger?: HTMLAttributes['class']
  comboboxTriggerLeading?: HTMLAttributes['class']
  comboboxTriggerTrailing?: HTMLAttributes['class']
  comboboxTriggerInfoIcon?: HTMLAttributes['class']
  comboboxTriggerSuccessIcon?: HTMLAttributes['class']
  comboboxTriggerWarningIcon?: HTMLAttributes['class']
  comboboxTriggerErrorIcon?: HTMLAttributes['class']
}
Components
 Combobox.vue
 ComboboxAnchor.vue
 ComboboxTrigger.vue
 ComboboxInput.vue
 ComboboxList.vue
 ComboboxViewport.vue
 ComboboxEmpty.vue
 ComboboxGroup.vue
 ComboboxLabel.vue
 ComboboxItem.vue
 ComboboxItemIndicator.vue
 ComboboxSeparator.vue
<script lang="ts">
import type { AcceptableValue, ComboboxRootEmits } from 'reka-ui'
import type { ExtractItemType, NComboboxGroupProps, NComboboxProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { defu } from 'defu'
import { ComboboxRoot, useForwardPropsEmits } from 'reka-ui'
import { cn } from '../../utils'
</script>
<script setup lang="ts" generic="T extends AcceptableValue, M extends boolean = false">
import { computed } from 'vue'
import ComboboxAnchor from './ComboboxAnchor.vue'
import ComboboxEmpty from './ComboboxEmpty.vue'
import ComboboxGroup from './ComboboxGroup.vue'
import ComboboxInput from './ComboboxInput.vue'
import ComboboxItem from './ComboboxItem.vue'
import ComboboxItemIndicator from './ComboboxItemIndicator.vue'
import ComboboxList from './ComboboxList.vue'
import ComboboxSeparator from './ComboboxSeparator.vue'
import ComboboxTrigger from './ComboboxTrigger.vue'
import ComboboxViewport from './ComboboxViewport.vue'
const props = withDefaults(defineProps<NComboboxProps<T, M>>(), {
  textEmpty: 'No items found.',
  size: 'sm',
})
const emits = defineEmits<ComboboxRootEmits<ExtractItemType<T>>>()
const rootProps = reactiveOmit(props, [
  'items',
  'una',
  'size',
  'label',
  'labelKey',
  'valueKey',
  'groupSeparator',
  'textEmpty',
  '_comboboxAnchor',
  '_comboboxEmpty',
  '_comboboxGroup',
  '_comboboxInput',
  '_comboboxItem',
  '_comboboxItemIndicator',
  '_comboboxLabel',
  '_comboboxList',
  '_comboboxSeparator',
  '_comboboxTrigger',
  '_comboboxViewport',
  '_comboboxCheckbox',
])
const forwarded = useForwardPropsEmits(rootProps, emits)
const labelKey = computed(() => props.labelKey?.toString() ?? 'label')
const valueKey = computed(() => props.valueKey?.toString() ?? 'value')
// Check if items are grouped
const hasGroups = computed(() => {
  return Array.isArray(props.items) && props.items.length > 0
    && typeof props.items[0] === 'object' && 'items' in (props.items[0] as any)
})
// Helper function to safely get a property from an item
function getItemProperty<K extends string>(item: ExtractItemType<T> | null | undefined, key: K): any {
  if (item == null)
    return ''
  return typeof item !== 'object' ? item : (item as Record<K, unknown>)[key]
}
// Find a matching item from the items list by its value
function findItemByValue(value: unknown): ExtractItemType<T> | undefined {
  if (!props.items)
    return undefined
  if (hasGroups.value) {
    // Search in grouped items
    for (const group of props.items as NComboboxGroupProps<ExtractItemType<T>>[]) {
      const found = group.items?.find(item => getItemProperty(item, valueKey.value) === value)
      if (found)
        return found
    }
    return undefined
  }
  else {
    // Search in flat items list
    return (props.items as ExtractItemType<T>[]).find(item => getItemProperty(item, valueKey.value) === value)
  }
}
// Display function that handles both single and multiple selections
function getDisplayValue(val: unknown): string {
  // Handle empty values
  if (!val || (Array.isArray(val) && val.length === 0))
    return ''
  // Handle multiple selection (array values)
  if (props.multiple && Array.isArray(val)) {
    return val.map((v) => {
      // For primitive values (string/number), find matching item to get label
      if (typeof v !== 'object' || v === null) {
        const item = findItemByValue(v)
        return item ? getItemProperty(item, labelKey.value) : v
      }
      // For objects, try to get the label directly
      return getItemProperty(v, labelKey.value) || getItemProperty(v, valueKey.value) || ''
    }).filter(Boolean).join(', ')
  }
  // For single primitive value
  if (typeof val !== 'object' || val === null) {
    const item = findItemByValue(val)
    return item ? getItemProperty(item, labelKey.value) : String(val || '')
  }
  // For single object, get its label
  return getItemProperty(val as any, labelKey.value) || getItemProperty(val as any, valueKey.value) || ''
}
// Check if an item is selected in the current modelValue
function isItemSelected(item: ExtractItemType<T> | null | undefined): boolean {
  if (item == null)
    return false
  const itemValue = getItemProperty(item, valueKey.value)
  // For multiple selection
  if (props.multiple && Array.isArray(props.modelValue)) {
    return props.modelValue.includes(itemValue)
  }
  // For single selection
  return typeof props.modelValue === 'object' && props.modelValue !== null
    ? getItemProperty(props.modelValue as ExtractItemType<T>, valueKey.value) === itemValue
    : props.modelValue === itemValue
}
</script>
<template>
  <ComboboxRoot
    data-slot="combobox"
    :class="cn(
      'combobox',
      props.una?.combobox,
      props.class,
    )"
    v-bind="forwarded"
  >
    <slot>
      <ComboboxAnchor
        v-bind="props._comboboxAnchor"
        :una
      >
        <slot name="anchor">
          <template
            v-if="$slots.trigger || $slots.triggerRoot"
          >
            <slot name="trigger-wrapper">
              <ComboboxTrigger
                v-bind="props._comboboxTrigger"
                :id
                :status
                :class="cn(
                  props._comboboxTrigger?.class,
                )"
                :size
              >
                <slot name="trigger" :model-value :open />
              </ComboboxTrigger>
            </slot>
          </template>
          <template v-else>
            <slot name="input-wrapper" :model-value :open>
              <ComboboxInput
                :id
                :display-value="(val: unknown) => getDisplayValue(val)"
                name="frameworks"
                :status
                :class="cn(
                  'text-1em',
                  props._comboboxInput?.class,
                )"
                :una="defu(props._comboboxInput?.una, {
                  inputLeading: 'text-1.1428571428571428em',
                  inputTrailing: 'text-1.1428571428571428em',
                  inputStatusIconBase: 'text-1.1428571428571428em',
                })"
                :size
                v-bind="props._comboboxInput"
              />
            </slot>
          </template>
        </slot>
      </ComboboxAnchor>
      <ComboboxList
        v-bind="{ ...props._comboboxList, ...props._comboboxContent }"
        :_combobox-portal
        :size
        :una
      >
        <slot name="list">
          <slot name="input-wrapper" :model-value :open>
            <ComboboxInput
              v-if="$slots.trigger || $slots.triggerRoot"
              :class="cn(
                'border-0 border-b-1 rounded-none text-1em focus-visible:ring-0',
                props._comboboxInput?.class,
              )"
              leading="combobox-input-leading-icon"
              :una="defu(props._comboboxInput?.una, {
                inputLeading: 'text-1.1428571428571428em',
                inputTrailing: 'text-1.1428571428571428em',
                inputStatusIconBase: 'text-1.1428571428571428em',
              })"
              :size
              v-bind="props._comboboxInput"
            />
          </slot>
          <slot name="header" />
          <slot name="body">
            <ComboboxViewport
              v-bind="props._comboboxViewport"
              :una
            >
              <ComboboxEmpty
                v-bind="props._comboboxEmpty"
                :class="cn(
                  props._comboboxEmpty?.class,
                )"
                :size
                :una
              >
                <slot name="empty">
                  {{ props.textEmpty }}
                </slot>
              </ComboboxEmpty>
              <!-- Non-grouped items -->
              <template v-if="!hasGroups">
                <ComboboxGroup
                  v-bind="props._comboboxGroup"
                  :label="props.label"
                  :una
                >
                  <slot name="group">
                    <ComboboxItem
                      v-for="item in items as ExtractItemType<T>[]"
                      :key="getItemProperty(item, valueKey)"
                      :value="props.multiple ? getItemProperty(item, valueKey) : item"
                      :size
                      v-bind="props._comboboxItem"
                      :class="cn(
                        props._comboboxItem?.class,
                      )"
                      :una
                    >
                      <slot name="item" :item="item" :selected="isItemSelected(item)">
                        <slot name="label" :item="item">
                          {{ getItemProperty(item, labelKey) }}
                        </slot>
                        <ComboboxItemIndicator
                          v-bind="props._comboboxItemIndicator"
                          :una
                        >
                          <slot name="item-indicator" :item="item">
                            <NIcon name="i-lucide-check" />
                          </slot>
                        </ComboboxItemIndicator>
                      </slot>
                    </ComboboxItem>
                  </slot>
                </ComboboxGroup>
              </template>
              <!-- Grouped items -->
              <template v-else>
                <ComboboxGroup
                  v-for="(group, i) in items as NComboboxGroupProps<ExtractItemType<T>>[]"
                  :key="i"
                  v-bind="props._comboboxGroup"
                  :label="group.label"
                  :una
                >
                  <ComboboxSeparator
                    v-if="i > 0 && props.groupSeparator"
                    v-bind="props._comboboxSeparator"
                    :una
                  />
                  <slot name="group" :group="group">
                    <ComboboxItem
                      v-for="item in group.items"
                      :key="getItemProperty(item, valueKey)"
                      :value="props.multiple ? getItemProperty(item, valueKey) : item"
                      :size
                      v-bind="{ ...props._comboboxItem, ...group._comboboxItem }"
                      :class="cn(
                        props._comboboxItem?.class,
                      )"
                      :una
                    >
                      <slot name="item" :item="item" :group="group" :selected="isItemSelected(item)">
                        <slot name="label" :item="item">
                          {{ getItemProperty(item, labelKey) }}
                        </slot>
                        <ComboboxItemIndicator
                          v-bind="props._comboboxItemIndicator"
                          :una
                        >
                          <slot name="indicator" :item="item" />
                        </ComboboxItemIndicator>
                      </slot>
                    </ComboboxItem>
                  </slot>
                </ComboboxGroup>
              </template>
            </ComboboxViewport>
          </slot>
          <slot name="footer" />
        </slot>
      </ComboboxList>
    </slot>
  </ComboboxRoot>
</template>
<script setup lang="ts">
import type { NComboboxAnchorProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxAnchor, useForwardProps } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NComboboxAnchorProps>()
const delegatedProps = reactiveOmit(props, ['class'])
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
  <ComboboxAnchor
    data-slot="combobox-anchor"
    v-bind="forwarded"
    :class="cn(
      'combobox-anchor',
      props.una?.comboboxAnchor,
      props.class,
    )"
  >
    <slot />
  </ComboboxAnchor>
</template>
<script setup lang="ts">
import type { NComboboxTriggerProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxTrigger, useForwardProps } from 'reka-ui'
import { computed } from 'vue'
import { cn, randomId } from '../../utils'
import Button from '../elements/Button.vue'
import Icon from '../elements/Icon.vue'
const props = withDefaults(defineProps<NComboboxTriggerProps>(), {
  btn: 'solid-white',
})
const forwardedProps = useForwardProps(reactiveOmit(props, 'class', 'status', 'una', 'btn'))
const statusClassVariants = computed(() => {
  const btn = {
    info: 'btn-outline-info',
    success: 'btn-outline-success',
    warning: 'btn-outline-warning',
    error: 'btn-outline-error',
    default: undefined,
  }
  const icon = {
    info: props.una?.comboboxTriggerInfoIcon ?? 'combobox-trigger-info-icon',
    success: props.una?.comboboxTriggerSuccessIcon ?? 'combobox-trigger-success-icon',
    warning: props.una?.comboboxTriggerWarningIcon ?? 'combobox-trigger-warning-icon',
    error: props.una?.comboboxTriggerErrorIcon ?? 'combobox-trigger-error-icon',
    default: props?.trailing ?? 'combobox-trigger-trailing-icon',
  }
  return {
    btn: btn[props.status ?? 'default'],
    icon: icon[props.status ?? 'default'],
  }
})
const id = computed(() => props.id ?? randomId('combobox-trigger'))
const status = computed(() => props.status ?? 'default')
</script>
<template>
  <ComboboxTrigger
    v-bind="forwardedProps"
    as-child
  >
    <slot name="wrapper">
      <Button
        :id
        :btn="statusClassVariants.btn ? undefined : props.btn"
        :data-status="status"
        data-slot="combobox-trigger"
        :class="cn(
          'combobox-trigger',
          props.class,
        )"
        tabindex="0"
        :size
        :una="{
          ...props.una,
          btn: props.una?.comboboxTrigger,
          btnLeading: cn(
            'combobox-trigger-leading',
            props.una?.btnLeading,
            props.una?.comboboxTriggerLeading,
          ),
          btnDefaultVariant: statusClassVariants.btn,
        }"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData" />
        </template>
        <template #trailing>
          <Icon
            :data-status="status"
            :name="statusClassVariants.icon"
            :class="cn(
              'combobox-trigger-trailing',
              props.una?.btnTrailing,
            )"
          />
        </template>
      </Button>
    </slot>
  </ComboboxTrigger>
</template>
<script setup lang="ts">
import type { ComboboxInputEmits } from 'reka-ui'
import type { NComboboxInputProps } from '../../types'
import { ComboboxInput, useForwardPropsEmits } from 'reka-ui'
import { inject, onMounted, ref } from 'vue'
import { isInComboboxListKey } from '../../utils/injectionKeys'
import Input from '../forms/Input.vue'
defineOptions({
  inheritAttrs: false,
})
const props = defineProps<NComboboxInputProps>()
const emits = defineEmits<ComboboxInputEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const inputRef = ref<InstanceType<typeof Input>>()
const isInList = inject(isInComboboxListKey, false)
onMounted(() => {
  if (isInList) {
    inputRef.value?.focus()
  }
})
</script>
<template>
  <ComboboxInput
    v-bind="props"
    as-child
  >
    <Input
      ref="inputRef"
      data-slot="command-input"
      v-bind="{ ...forwarded, ...$attrs }"
      :_input-wrapper="{
        'data-slot': 'command-input-wrapper',
      }"
    >
      <template v-for="(_, name) in $slots" #[name]="slotData">
        <slot :name="name" v-bind="slotData" />
      </template>
    </Input>
  </ComboboxInput>
</template>
<script setup lang="ts">
import type { ComboboxContentEmits } from 'reka-ui'
import type { NComboboxListProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxContent, ComboboxPortal, useForwardPropsEmits } from 'reka-ui'
import { provide } from 'vue'
import { cn } from '../../utils'
import { isInComboboxListKey } from '../../utils/injectionKeys'
const props = withDefaults(defineProps<NComboboxListProps>(), {
  position: 'popper',
  align: 'center',
  sideOffset: 4,
})
const emits = defineEmits<ComboboxContentEmits>()
provide(isInComboboxListKey, true)
const delegatedProps = reactiveOmit(props, 'class', 'viewportClass')
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
  <ComboboxPortal
    v-bind="props._comboboxPortal"
  >
    <ComboboxContent
      data-slot="combobox-list"
      v-bind="forwarded"
      :class="cn(
        'origin-(--reka-combobox-content-transform-origin)',
        'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
        'combobox-list',
        props.una?.comboboxList,
        props.class,
      )"
    >
      <slot />
    </ComboboxContent>
  </ComboboxPortal>
</template>
<script setup lang="ts">
import type { NComboboxViewportProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxViewport, useForwardProps } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NComboboxViewportProps>()
const delegatedProps = reactiveOmit(props, ['class'])
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
  <ComboboxViewport
    data-slot="combobox-viewport"
    v-bind="forwarded"
    :class="cn(
      'combobox-viewport',
      props.una?.comboboxViewport,
      props.class,
    )"
  >
    <slot />
  </ComboboxViewport>
</template>
<script setup lang="ts">
import type { NComboboxEmptyProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxEmpty } from 'reka-ui'
import { cn } from '../../utils'
const props = withDefaults(defineProps<NComboboxEmptyProps>(), {
  size: 'sm',
})
const delegatedProps = reactiveOmit(props, ['class'])
</script>
<template>
  <ComboboxEmpty
    data-slot="combobox-empty"
    v-bind="delegatedProps"
    :class="cn(
      'combobox-empty',
      props.una?.comboboxEmpty,
      props.class,
    )"
  >
    <slot />
  </ComboboxEmpty>
</template>
<script lang="ts">
import type { AcceptableValue } from 'reka-ui'
</script>
<script setup lang="ts" generic="T extends AcceptableValue">
import type { NComboboxGroupProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxGroup } from 'reka-ui'
import { cn } from '../../utils'
import ComboboxLabel from './ComboboxLabel.vue'
const props = defineProps<NComboboxGroupProps<T>>()
const delegatedProps = reactiveOmit(props, ['class'])
</script>
<template>
  <ComboboxGroup
    data-slot="combobox-group"
    v-bind="delegatedProps"
    :class="cn(
      'combobox-group',
      props.una?.comboboxGroup,
      props.class,
    )"
  >
    <ComboboxLabel
      v-if="props.label"
      :una
    >
      {{ props.label }}
    </ComboboxLabel>
    <slot />
  </ComboboxGroup>
</template>
<script setup lang="ts">
import type { NComboboxLabelProps } from '../../types'
import { ComboboxLabel } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NComboboxLabelProps>()
</script>
<template>
  <ComboboxLabel
    :class="cn(
      'combobox-label',
      props.una?.comboboxLabel,
      props.class,
    )"
  >
    <slot>
      {{ props.label }}
    </slot>
  </ComboboxLabel>
</template>
<script setup lang="ts" generic="T">
import type { ComboboxItemEmits } from 'reka-ui'
import type { NComboboxItemProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxItem, useForwardPropsEmits } from 'reka-ui'
import { cn } from '../../utils'
const props = withDefaults(defineProps<NComboboxItemProps<T>>(), {
  size: 'sm',
})
const emits = defineEmits<ComboboxItemEmits>()
const delegatedProps = reactiveOmit(props, ['class'])
const forwarded = useForwardPropsEmits(delegatedProps, emits)
</script>
<template>
  <ComboboxItem
    data-slot="combobox-item"
    v-bind="forwarded"
    :class="cn(
      `combobox-item`,
      props.una?.comboboxItem,
      props.class,
    )"
  >
    <slot />
  </ComboboxItem>
</template>
<script setup lang="ts">
import type { NComboboxItemIndicatorProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxItemIndicator, useForwardProps } from 'reka-ui'
import { cn } from '../../utils'
const props = withDefaults(defineProps<NComboboxItemIndicatorProps>(), {
  icon: 'combobox-item-indicator-icon-name',
})
const delegatedProps = reactiveOmit(props, ['class'])
const forwarded = useForwardProps(delegatedProps)
</script>
<template>
  <ComboboxItemIndicator
    data-slot="combobox-item-indicator"
    v-bind="forwarded"
    :class="cn(
      'combobox-item-indicator',
      props.una?.comboboxItemIndicator,
      props.class,
    )"
  >
    <slot>
      <NIcon
        :name="props.icon"
        :class="cn(
          'combobox-item-indicator-icon',
          props.una?.comboboxItemIndicatorIcon,
        )"
      />
    </slot>
  </ComboboxItemIndicator>
</template>
<script setup lang="ts">
import type { NComboboxSeparatorProps } from '../../types'
import { reactiveOmit } from '@vueuse/core'
import { ComboboxSeparator } from 'reka-ui'
import { cn } from '../../utils'
const props = defineProps<NComboboxSeparatorProps>()
const delegatedProps = reactiveOmit(props, ['class'])
</script>
<template>
  <ComboboxSeparator
    data-slot="combobox-separator"
    v-bind="delegatedProps"
    :class="cn(
      'combobox-separator',
      props.una?.comboboxSeparator,
      props.class,
    )"
  >
    <slot />
  </ComboboxSeparator>
</template>