Select

SelectGitLab
Um elemento de seleção para escolher em uma lista de opções.

Uso

Use a diretiva v-model para controlar o valor do Select ou a prop default-value para definir o valor inicial quando você não precisa controlar o estado.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" :items="items" />
</template>

Itens

Use a prop items como um array de strings, números ou booleanos:

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>

Você também pode passar um array de objetos com as seguintes propriedades:

  • label?: string
  • value?: string
  • type?: "label" | "separator" | "item"
  • icon?: string
  • avatar?: AvatarProps
  • chip?: ChipProps
  • disabled?: boolean
  • class?: any
  • ui?: { label?: ClassNameValue, separator?: ClassNameValue, item?: ClassNameValue, itemLeadingIcon?: ClassNameValue, itemLeadingAvatarSize?: ClassNameValue, itemLeadingAvatar?: ClassNameValue, itemLeadingChipSize?: ClassNameValue, itemLeadingChip?: ClassNameValue, itemLabel?: ClassNameValue, itemTrailing?: ClassNameValue, itemTrailingIcon?: ClassNameValue }
<script setup lang="ts">
import type { SelectItem } from '@nitro/ui'

const items = ref<SelectItem[]>([
  {
    label: 'Backlog',
    value: 'backlog'
  },
  {
    label: 'Todo',
    value: 'todo'
  },
  {
    label: 'In Progress',
    value: 'in_progress'
  },
  {
    label: 'Done',
    value: 'done'
  }
])
const value = ref('backlog')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { SelectItem } from '@nitro/ui'

const items = ref<SelectItem[]>([
  {
    label: 'Backlog',
    value: 'backlog'
  },
  {
    label: 'Todo',
    value: 'todo'
  },
  {
    label: 'In Progress',
    value: 'in_progress'
  },
  {
    label: 'Done',
    value: 'done'
  }
])
const value = ref('backlog')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>
Ao usar objetos, você precisa referenciar a propriedade value do objeto na diretiva v-model ou na prop default-value.

Você também pode passar um array de arrays para a prop items para exibir grupos separados de itens.

<script setup lang="ts">
const items = ref([
  ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'],
  ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
])
const value = ref('Apple')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref([
  ['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'],
  ['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
])
const value = ref('Apple')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>

Chave de valor

Você pode alterar a propriedade usada para definir o valor usando a prop value-key. O padrão é value.

<script setup lang="ts">
import type { SelectItem } from '@nitro/ui'

const items = ref<SelectItem[]>([
  {
    label: 'Backlog',
    id: 'backlog'
  },
  {
    label: 'Todo',
    id: 'todo'
  },
  {
    label: 'In Progress',
    id: 'in_progress'
  },
  {
    label: 'Done',
    id: 'done'
  }
])
const value = ref('backlog')
</script>

<template>
  <NSelect v-model="value" value-key="id" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { SelectItem } from '@nitro/ui'

const items = ref<SelectItem[]>([
  {
    label: 'Backlog',
    id: 'backlog'
  },
  {
    label: 'Todo',
    id: 'todo'
  },
  {
    label: 'In Progress',
    id: 'in_progress'
  },
  {
    label: 'Done',
    id: 'done'
  }
])
const value = ref('backlog')
</script>

<template>
  <NSelect v-model="value" value-key="id" :items="items" class="w-48" />
</template>

Múltiplo

Use a prop multiple para permitir múltiplas seleções; os itens selecionados serão separados por vírgula no gatilho.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref(['Backlog', 'Todo'])
</script>

<template>
  <NSelect v-model="value" multiple :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref(['Backlog', 'Todo'])
</script>

<template>
  <NSelect v-model="value" multiple :items="items" class="w-48" />
</template>
Certifique-se de passar um array para a prop default-value ou para a diretiva v-model.

Placeholder

Use a prop placeholder para definir um texto de placeholder.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
</script>

<template>
  <NSelect placeholder="Select status" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
</script>

<template>
  <NSelect placeholder="Select status" :items="items" class="w-48" />
</template>

Conteúdo

Use a prop content para controlar como o conteúdo do Select é renderizado, como seu align ou side, por exemplo.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect
    v-model="value"
    :content="{
      align: 'center',
      side: 'bottom',
      sideOffset: 8
    }"
    :items="items"
    class="w-48"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect
    v-model="value"
    :content="{
      align: 'center',
      side: 'bottom',
      sideOffset: 8
    }"
    :items="items"
    class="w-48"
  />
</template>
Essas opções só se aplicam quando content.position é popper (padrão).

Posição 4.7+

Use a prop content.position para controlar como o conteúdo do Select é posicionado em relação ao gatilho. O padrão é popper, que posiciona o conteúdo como outros popovers. Defina-a como item-aligned para alinhar o conteúdo com o item selecionado (semelhante a um menu nativo do macOS).

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Todo')
</script>

<template>
  <NSelect
    v-model="value"
    :content="{
      position: 'item-aligned'
    }"
    :items="items"
    class="w-48"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Todo')
</script>

<template>
  <NSelect
    v-model="value"
    :content="{
      position: 'item-aligned'
    }"
    :items="items"
    class="w-48"
  />
</template>

Seta

Use a prop arrow para exibir uma seta no Select.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" arrow :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" arrow :items="items" class="w-48" />
</template>

Cor

Use a prop color para alterar a cor do ring quando o Select está em foco.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" color="neutral" highlight :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" color="neutral" highlight :items="items" class="w-48" />
</template>
A prop highlight é usada aqui para mostrar o estado de foco. Ela é usada internamente quando ocorre um erro de validação.

Variante

Use a prop variant para alterar a variante do Select.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" color="neutral" variant="subtle" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" color="neutral" variant="subtle" :items="items" class="w-48" />
</template>

Tamanho

Use a prop size para alterar o tamanho do Select.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" size="xl" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" size="xl" :items="items" class="w-48" />
</template>

Ícone

Use a prop icon para exibir um Icon dentro do Select.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" icon="i-lucide-search" size="md" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" icon="i-lucide-search" size="md" :items="items" class="w-48" />
</template>

Ícone à direita

Use a prop trailing-icon para personalizar o Icon à direita. O padrão é i-lucide-chevron-down.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect
    v-model="value"
    trailing-icon="i-lucide-arrow-down"
    size="md"
    :items="items"
    class="w-48"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect
    v-model="value"
    trailing-icon="i-lucide-arrow-down"
    size="md"
    :items="items"
    class="w-48"
  />
</template>
Você pode personalizar esse ícone globalmente no seu app.config.ts na chave ui.icons.chevronDown.
Você pode personalizar esse ícone globalmente no seu vite.config.ts na chave ui.icons.chevronDown.

Ícone de selecionado

Use a prop selected-icon para personalizar o ícone quando um item é selecionado. O padrão é i-lucide-check.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" selected-icon="i-lucide-flame" size="md" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" selected-icon="i-lucide-flame" size="md" :items="items" class="w-48" />
</template>
Você pode personalizar esse ícone globalmente no seu app.config.ts na chave ui.icons.check.
Você pode personalizar esse ícone globalmente no seu vite.config.ts na chave ui.icons.check.

Avatar

Use a prop avatar para exibir um Avatar dentro do Select.

<script setup lang="ts">
const items = ref(['Nuxt', 'NuxtHub', 'NuxtLabs', 'Nuxt Modules', 'Nuxt Community'])
const value = ref('Nuxt')
</script>

<template>
  <NSelect
    v-model="value"
    :avatar="{
      src: 'https://github.com/nuxt.png',
      loading: 'lazy'
    }"
    :items="items"
    class="w-48"
  />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Nuxt', 'NuxtHub', 'NuxtLabs', 'Nuxt Modules', 'Nuxt Community'])
const value = ref('Nuxt')
</script>

<template>
  <NSelect
    v-model="value"
    :avatar="{
      src: 'https://github.com/nuxt.png',
      loading: 'lazy'
    }"
    :items="items"
    class="w-48"
  />
</template>

Carregando

Use a prop loading para exibir um ícone de carregamento no Select.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" loading :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" loading :items="items" class="w-48" />
</template>

Ícone de carregamento

Use a prop loading-icon para personalizar o ícone de carregamento. O padrão é i-lucide-loader-circle.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" loading loading-icon="i-lucide-loader" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>

<template>
  <NSelect v-model="value" loading loading-icon="i-lucide-loader" :items="items" class="w-48" />
</template>
Você pode personalizar esse ícone globalmente no seu app.config.ts na chave ui.icons.loading.
Você pode personalizar esse ícone globalmente no seu vite.config.ts na chave ui.icons.loading.

Desabilitado

Use a prop disabled para desabilitar o Select.

<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
</script>

<template>
  <NSelect disabled placeholder="Select status" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
</script>

<template>
  <NSelect disabled placeholder="Select status" :items="items" class="w-48" />
</template>

Exemplos

Com tipo de itens

Você pode usar a propriedade type com separator para exibir um separador entre os itens ou label para exibir um rótulo.

<script setup lang="ts">
import type { SelectItem } from '@nitro/ui'

const items = ref<SelectItem[]>([
  {
    type: 'label',
    label: 'Fruits'
  },
  'Apple',
  'Banana',
  'Blueberry',
  'Grapes',
  'Pineapple',
  {
    type: 'separator'
  },
  {
    type: 'label',
    label: 'Vegetables'
  },
  'Aubergine',
  'Broccoli',
  'Carrot',
  'Courgette',
  'Leek'
])
const value = ref('Apple')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { SelectItem } from '@nitro/ui'

const items = ref<SelectItem[]>([
  {
    type: 'label',
    label: 'Fruits'
  },
  'Apple',
  'Banana',
  'Blueberry',
  'Grapes',
  'Pineapple',
  {
    type: 'separator'
  },
  {
    type: 'label',
    label: 'Vegetables'
  },
  'Aubergine',
  'Broccoli',
  'Carrot',
  'Courgette',
  'Leek'
])
const value = ref('Apple')
</script>

<template>
  <NSelect v-model="value" :items="items" class="w-48" />
</template>

Com ícone nos itens

Você pode usar a propriedade icon para exibir um Icon dentro dos itens.

Neste exemplo, o ícone é calculado a partir da propriedade value do item selecionado.
Você também pode usar o slot #leading para exibir o ícone selecionado.

Com avatar nos itens

Você pode usar a propriedade avatar para exibir um Avatar dentro dos itens.

Neste exemplo, o avatar é calculado a partir da propriedade value do item selecionado.
Você também pode usar o slot #leading para exibir o avatar selecionado.

Com chip nos itens

Você pode usar a propriedade chip para exibir um Chip dentro dos itens.

Neste exemplo, o slot #leading é usado para exibir o chip selecionado.

Controlar o estado de abertura

Você pode controlar o estado de abertura usando a prop default-open ou a diretiva v-model:open.

Neste exemplo, aproveitando o defineShortcuts, você pode abrir e fechar o Select pressionando O.

Com ícone rotativo

Aqui está um exemplo com um ícone rotativo que indica o estado de abertura do Select.

Com itens buscados

Você pode buscar itens de uma API e usá-los no Select.

Este exemplo usa useLazyFetch com immediate: false para buscar os dados apenas quando o menu abre, evitando chamadas de API desnecessárias no carregamento da página.

Com rolagem infinita 4.4+

Você pode usar o composable useInfiniteScroll para carregar mais dados conforme o usuário rola.

Este exemplo usa useLazyFetch com immediate: false, então os dados só são carregados conforme o usuário rola.

Com largura total do conteúdo

Você pode expandir o conteúdo até a largura total dos seus itens adicionando a classe min-w-fit no slot ui.content.

Você também pode alterar a largura do conteúdo globalmente no seu app.config.ts:
export default defineAppConfig({
  ui: {
    select: {
      slots: {
        content: 'min-w-fit'
      }
    }
  }
})

API

Props

Prop Default Type
id string
placeholder string

The placeholder text when the select is empty.

color'primary' "primary" | "secondary" | "accent" | "success" | "info" | "warning" | "error" | "neutral"
variant'outline' "outline" | "soft" | "subtle" | "ghost" | "none"
size'md' "sm" | "md" | "xs" | "lg" | "xl"
trailingIconappConfig.ui.icons.chevronDownany

The icon displayed to open the menu.

selectedIconappConfig.ui.icons.checkany

The icon displayed when an item is selected.

content{ side: 'bottom', sideOffset: 8, collisionPadding: 8, position: 'popper' } SelectContentProps & Partial<EmitsToProps<SelectContentImplEmits>>

The content of the menu.

arrowfalseboolean | SelectArrowProps

Display an arrow alongside the menu. { rounded: true }

portaltrue string | false | true | HTMLElement

Render the menu in a portal.

valueKey'value' VK

When items is an array of objects, select the field to use as the value.

labelKey'label' keyof Extract<NestedItem<T>, object> & string | DotPathKeys<Extract<NestedItem<T>, object>>

When items is an array of objects, select the field to use as the label.

descriptionKey'description' keyof Extract<NestedItem<T>, object> & string | DotPathKeys<Extract<NestedItem<T>, object>>

When items is an array of objects, select the field to use as the description.

items T
defaultValue _Number<_Optional<_Nullable<GetModelValue<T, VK, M, ExcludeItem>, Mod>, Mod>, Mod>

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

modelValue _Number<_Optional<_Nullable<GetModelValue<T, VK, M, ExcludeItem>, Mod>, Mod>, Mod>

The controlled value of the Select. Can be bind as v-model.

modelModifiers Mod
multiple M

Whether multiple options can be selected or not.

highlightboolean

Highlight the ring color like a focus state.

autofocusboolean
autofocusDelay0 number
disabledboolean

When true, prevents the user from interacting with Select

openboolean

The controlled open state of the Select. Can be bind as v-model:open.

defaultOpenboolean

The open state of the select when it is initially rendered. Use when you do not need to control its open state.

nullableValue string

The value of the hidden native select option when the model value is nullish.

autocomplete string

Native html input autocomplete attribute.

name string

The name of the field. Submitted with its owning form as part of a name/value pair.

requiredboolean

When true, indicates that the user must set the value before the owning form can be submitted.

iconany

Display an icon based on the leading and trailing props.

avatar AvatarProps

Display an avatar on the left side.

leadingboolean

When true, the icon will be displayed on the left side.

leadingIconany

Display an icon on the left side.

trailingboolean

When true, the icon will be displayed on the right side.

loadingboolean

When true, the loading icon will be displayed.

loadingIconappConfig.ui.icons.loadingany

The icon when the loading prop is true.

ui { base?: SlotClass; leading?: SlotClass; leadingIcon?: SlotClass; leadingAvatar?: SlotClass; leadingAvatarSize?: SlotClass; trailing?: SlotClass; trailingIcon?: SlotClass; value?: SlotClass; placeholder?: SlotClass; arrow?: SlotClass; content?: SlotClass; viewport?: SlotClass; group?: SlotClass; empty?: SlotClass; label?: SlotClass; separator?: SlotClass; item?: SlotClass; itemLeadingIcon?: SlotClass; itemLeadingAvatar?: SlotClass; itemLeadingAvatarSize?: SlotClass; itemLeadingChip?: SlotClass; itemLeadingChipSize?: SlotClass; itemTrailing?: SlotClass; itemTrailingIcon?: SlotClass; itemWrapper?: SlotClass; itemLabel?: SlotClass; itemDescription?: SlotClass; }
Este componente também suporta todos os atributos HTML nativos de <button>.

Slots

Slot Type
leading{ modelValue: _Number<_Optional<_Nullable<GetModelValue<T, VK, M, ExcludeItem>, Mod>, Mod>, Mod>; open: boolean; ui: object; }
default{ modelValue: _Number<_Optional<_Nullable<GetModelValue<T, VK, M, ExcludeItem>, Mod>, Mod>, Mod>; open: boolean; ui: object; }
trailing{ modelValue: _Number<_Optional<_Nullable<GetModelValue<T, VK, M, ExcludeItem>, Mod>, Mod>, Mod>; open: boolean; ui: object; }
item{ item: NestedItem<T>; index: number; ui: object; }
item-leading{ item: NestedItem<T>; index: number; ui: object; }
item-label{ item: NestedItem<T>; index: number; }
item-description{ item: NestedItem<T>; index: number; }
item-trailing{ item: NestedItem<T>; index: number; ui: object; }
content-top{}
content-bottom{}

Emits

Event Type
update:modelValue[value: _Number<_Optional<_Nullable<GetModelValue<T, VK, M, ExcludeItem>, Mod>, Mod>, Mod>]
update:open[value: boolean]
change[event: Event]
blur[event: FocusEvent]
focus[event: FocusEvent]

Expose

Ao acessar o componente por meio de um template ref, você pode usar o seguinte:

NameType
triggerRefRef<HTMLButtonElement | null>
viewportRefRef<HTMLDivElement | null>

Tema

app.config.ts
export default defineAppConfig({
  ui: {
    select: {
      slots: {
        base: [
          'relative group rounded-md inline-flex items-center disabled:cursor-not-allowed disabled:opacity-75',
          'transition-colors'
        ],
        leading: 'absolute inset-y-0 start-0 flex items-center',
        leadingIcon: 'shrink-0 text-dimmed',
        leadingAvatar: 'shrink-0',
        leadingAvatarSize: '',
        trailing: 'absolute inset-y-0 end-0 flex items-center',
        trailingIcon: 'shrink-0 text-dimmed',
        value: 'truncate pointer-events-none',
        placeholder: 'truncate text-dimmed',
        arrow: 'fill-bg stroke-default',
        content: 'max-h-[min(15rem,var(--reka-select-content-available-height,15rem))] w-(--reka-select-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden origin-(--reka-select-content-transform-origin) pointer-events-auto flex flex-col',
        viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
        group: 'p-1 isolate',
        empty: 'text-center text-muted',
        label: 'font-semibold text-highlighted',
        separator: '-mx-1 my-1 h-px bg-border',
        item: [
          'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
          'transition-colors before:transition-colors'
        ],
        itemLeadingIcon: [
          'shrink-0 text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
          'transition-colors'
        ],
        itemLeadingAvatar: 'shrink-0',
        itemLeadingAvatarSize: '',
        itemLeadingChip: 'shrink-0',
        itemLeadingChipSize: '',
        itemTrailing: 'ms-auto inline-flex gap-1.5 items-center',
        itemTrailingIcon: 'shrink-0',
        itemWrapper: 'flex-1 flex flex-col min-w-0',
        itemLabel: 'truncate',
        itemDescription: 'truncate text-muted'
      },
      variants: {
        fieldGroup: {
          horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
          vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
        },
        size: {
          xs: {
            base: 'px-2 py-1 text-xs gap-1',
            leading: 'ps-2',
            trailing: 'pe-2',
            leadingIcon: 'size-4',
            leadingAvatarSize: '3xs',
            trailingIcon: 'size-4',
            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',
            itemTrailingIcon: 'size-4',
            empty: 'p-2 text-xs'
          },
          sm: {
            base: 'px-2.5 py-1.5 text-xs gap-1.5',
            leading: 'ps-2.5',
            trailing: 'pe-2.5',
            leadingIcon: 'size-4',
            leadingAvatarSize: '3xs',
            trailingIcon: 'size-4',
            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',
            itemTrailingIcon: 'size-4',
            empty: 'p-2.5 text-xs'
          },
          md: {
            base: 'px-2.5 py-1.5 text-sm gap-1.5',
            leading: 'ps-2.5',
            trailing: 'pe-2.5',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs',
            trailingIcon: 'size-5',
            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',
            itemTrailingIcon: 'size-5',
            empty: 'p-2.5 text-sm'
          },
          lg: {
            base: 'px-3 py-2 text-sm gap-2',
            leading: 'ps-3',
            trailing: 'pe-3',
            leadingIcon: 'size-5',
            leadingAvatarSize: '2xs',
            trailingIcon: 'size-5',
            label: 'p-2 text-xs gap-2',
            item: 'p-2 text-sm gap-2',
            itemLeadingIcon: 'size-5',
            itemLeadingAvatarSize: '2xs',
            itemLeadingChip: 'size-5',
            itemLeadingChipSize: 'md',
            itemTrailingIcon: 'size-5',
            empty: 'p-3 text-sm'
          },
          xl: {
            base: 'px-3 py-2 text-base gap-2',
            leading: 'ps-3',
            trailing: 'pe-3',
            leadingIcon: 'size-6',
            leadingAvatarSize: 'xs',
            trailingIcon: 'size-6',
            label: 'p-2 text-sm gap-2',
            item: 'p-2 text-base gap-2',
            itemLeadingIcon: 'size-6',
            itemLeadingAvatarSize: 'xs',
            itemLeadingChip: 'size-6',
            itemLeadingChipSize: 'lg',
            itemTrailingIcon: 'size-6',
            empty: 'p-3 text-base'
          }
        },
        variant: {
          outline: 'text-highlighted bg-default ring ring-inset ring-accented hover:bg-elevated disabled:bg-default',
          soft: 'text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50',
          subtle: 'text-highlighted bg-elevated ring ring-inset ring-accented hover:bg-accented/75 disabled:bg-elevated',
          ghost: 'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
          none: 'text-highlighted bg-transparent focus:outline-none'
        },
        color: {
          primary: '',
          secondary: '',
          accent: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        leading: {
          true: ''
        },
        trailing: {
          true: ''
        },
        loading: {
          true: ''
        },
        highlight: {
          true: ''
        },
        fixed: {
          false: ''
        },
        type: {
          file: 'file:me-1.5 file:font-medium file:text-muted file:outline-none'
        },
        position: {
          popper: {
            content: 'data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]'
          },
          'item-aligned': {
            content: ''
          }
        },
        multiple: {
          true: ''
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'outline-primary/25 focus-visible:outline-3 focus-visible:ring-primary'
        },
        {
          color: 'primary',
          variant: [
            'soft',
            'ghost'
          ],
          class: 'outline-primary/25 focus-visible:outline-3'
        },
        {
          color: 'primary',
          highlight: true,
          class: 'ring ring-inset ring-primary'
        },
        {
          color: 'neutral',
          variant: [
            'outline',
            'subtle'
          ],
          class: 'outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted'
        },
        {
          color: 'neutral',
          variant: [
            'soft',
            'ghost'
          ],
          class: 'outline-inverted/25 focus-visible:outline-3'
        },
        {
          color: 'neutral',
          highlight: true,
          class: 'ring ring-inset ring-inverted'
        },
        {
          leading: true,
          size: 'xs',
          class: 'ps-7'
        },
        {
          leading: true,
          size: 'sm',
          class: 'ps-8'
        },
        {
          leading: true,
          size: 'md',
          class: 'ps-9'
        },
        {
          leading: true,
          size: 'lg',
          class: 'ps-10'
        },
        {
          leading: true,
          size: 'xl',
          class: 'ps-11'
        },
        {
          trailing: true,
          size: 'xs',
          class: 'pe-7'
        },
        {
          trailing: true,
          size: 'sm',
          class: 'pe-8'
        },
        {
          trailing: true,
          size: 'md',
          class: 'pe-9'
        },
        {
          trailing: true,
          size: 'lg',
          class: 'pe-10'
        },
        {
          trailing: true,
          size: 'xl',
          class: 'pe-11'
        },
        {
          loading: true,
          leading: true,
          class: {
            leadingIcon: 'animate-spin'
          }
        },
        {
          loading: true,
          leading: false,
          trailing: true,
          class: {
            trailingIcon: 'animate-spin'
          }
        },
        {
          fixed: false,
          size: 'xs',
          class: 'md:text-xs'
        },
        {
          fixed: false,
          size: 'sm',
          class: 'md:text-xs'
        },
        {
          fixed: false,
          size: 'md',
          class: 'md:text-sm'
        },
        {
          fixed: false,
          size: 'lg',
          class: 'md:text-sm'
        }
      ],
      defaultVariants: {
        size: 'md',
        color: 'accent',
        variant: 'outline',
        position: 'popper'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nitro/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        select: {
          slots: {
            base: [
              'relative group rounded-md inline-flex items-center disabled:cursor-not-allowed disabled:opacity-75',
              'transition-colors'
            ],
            leading: 'absolute inset-y-0 start-0 flex items-center',
            leadingIcon: 'shrink-0 text-dimmed',
            leadingAvatar: 'shrink-0',
            leadingAvatarSize: '',
            trailing: 'absolute inset-y-0 end-0 flex items-center',
            trailingIcon: 'shrink-0 text-dimmed',
            value: 'truncate pointer-events-none',
            placeholder: 'truncate text-dimmed',
            arrow: 'fill-bg stroke-default',
            content: 'max-h-[min(15rem,var(--reka-select-content-available-height,15rem))] w-(--reka-select-trigger-width) bg-default shadow-lg rounded-md ring ring-default overflow-hidden origin-(--reka-select-content-transform-origin) pointer-events-auto flex flex-col',
            viewport: 'relative divide-y divide-default scroll-py-1 overflow-y-auto flex-1',
            group: 'p-1 isolate',
            empty: 'text-center text-muted',
            label: 'font-semibold text-highlighted',
            separator: '-mx-1 my-1 h-px bg-border',
            item: [
              'group relative w-full flex items-start select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-md data-disabled:cursor-not-allowed data-disabled:opacity-75 text-default data-highlighted:not-data-disabled:text-highlighted data-highlighted:not-data-disabled:before:bg-elevated/50',
              'transition-colors before:transition-colors'
            ],
            itemLeadingIcon: [
              'shrink-0 text-dimmed group-data-highlighted:not-group-data-disabled:text-default',
              'transition-colors'
            ],
            itemLeadingAvatar: 'shrink-0',
            itemLeadingAvatarSize: '',
            itemLeadingChip: 'shrink-0',
            itemLeadingChipSize: '',
            itemTrailing: 'ms-auto inline-flex gap-1.5 items-center',
            itemTrailingIcon: 'shrink-0',
            itemWrapper: 'flex-1 flex flex-col min-w-0',
            itemLabel: 'truncate',
            itemDescription: 'truncate text-muted'
          },
          variants: {
            fieldGroup: {
              horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none focus-visible:z-[1]',
              vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none focus-visible:z-[1]'
            },
            size: {
              xs: {
                base: 'px-2 py-1 text-xs gap-1',
                leading: 'ps-2',
                trailing: 'pe-2',
                leadingIcon: 'size-4',
                leadingAvatarSize: '3xs',
                trailingIcon: 'size-4',
                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',
                itemTrailingIcon: 'size-4',
                empty: 'p-2 text-xs'
              },
              sm: {
                base: 'px-2.5 py-1.5 text-xs gap-1.5',
                leading: 'ps-2.5',
                trailing: 'pe-2.5',
                leadingIcon: 'size-4',
                leadingAvatarSize: '3xs',
                trailingIcon: 'size-4',
                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',
                itemTrailingIcon: 'size-4',
                empty: 'p-2.5 text-xs'
              },
              md: {
                base: 'px-2.5 py-1.5 text-sm gap-1.5',
                leading: 'ps-2.5',
                trailing: 'pe-2.5',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs',
                trailingIcon: 'size-5',
                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',
                itemTrailingIcon: 'size-5',
                empty: 'p-2.5 text-sm'
              },
              lg: {
                base: 'px-3 py-2 text-sm gap-2',
                leading: 'ps-3',
                trailing: 'pe-3',
                leadingIcon: 'size-5',
                leadingAvatarSize: '2xs',
                trailingIcon: 'size-5',
                label: 'p-2 text-xs gap-2',
                item: 'p-2 text-sm gap-2',
                itemLeadingIcon: 'size-5',
                itemLeadingAvatarSize: '2xs',
                itemLeadingChip: 'size-5',
                itemLeadingChipSize: 'md',
                itemTrailingIcon: 'size-5',
                empty: 'p-3 text-sm'
              },
              xl: {
                base: 'px-3 py-2 text-base gap-2',
                leading: 'ps-3',
                trailing: 'pe-3',
                leadingIcon: 'size-6',
                leadingAvatarSize: 'xs',
                trailingIcon: 'size-6',
                label: 'p-2 text-sm gap-2',
                item: 'p-2 text-base gap-2',
                itemLeadingIcon: 'size-6',
                itemLeadingAvatarSize: 'xs',
                itemLeadingChip: 'size-6',
                itemLeadingChipSize: 'lg',
                itemTrailingIcon: 'size-6',
                empty: 'p-3 text-base'
              }
            },
            variant: {
              outline: 'text-highlighted bg-default ring ring-inset ring-accented hover:bg-elevated disabled:bg-default',
              soft: 'text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50',
              subtle: 'text-highlighted bg-elevated ring ring-inset ring-accented hover:bg-accented/75 disabled:bg-elevated',
              ghost: 'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
              none: 'text-highlighted bg-transparent focus:outline-none'
            },
            color: {
              primary: '',
              secondary: '',
              accent: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            leading: {
              true: ''
            },
            trailing: {
              true: ''
            },
            loading: {
              true: ''
            },
            highlight: {
              true: ''
            },
            fixed: {
              false: ''
            },
            type: {
              file: 'file:me-1.5 file:font-medium file:text-muted file:outline-none'
            },
            position: {
              popper: {
                content: 'data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]'
              },
              'item-aligned': {
                content: ''
              }
            },
            multiple: {
              true: ''
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              variant: [
                'outline',
                'subtle'
              ],
              class: 'outline-primary/25 focus-visible:outline-3 focus-visible:ring-primary'
            },
            {
              color: 'primary',
              variant: [
                'soft',
                'ghost'
              ],
              class: 'outline-primary/25 focus-visible:outline-3'
            },
            {
              color: 'primary',
              highlight: true,
              class: 'ring ring-inset ring-primary'
            },
            {
              color: 'neutral',
              variant: [
                'outline',
                'subtle'
              ],
              class: 'outline-inverted/25 focus-visible:outline-3 focus-visible:ring-inverted'
            },
            {
              color: 'neutral',
              variant: [
                'soft',
                'ghost'
              ],
              class: 'outline-inverted/25 focus-visible:outline-3'
            },
            {
              color: 'neutral',
              highlight: true,
              class: 'ring ring-inset ring-inverted'
            },
            {
              leading: true,
              size: 'xs',
              class: 'ps-7'
            },
            {
              leading: true,
              size: 'sm',
              class: 'ps-8'
            },
            {
              leading: true,
              size: 'md',
              class: 'ps-9'
            },
            {
              leading: true,
              size: 'lg',
              class: 'ps-10'
            },
            {
              leading: true,
              size: 'xl',
              class: 'ps-11'
            },
            {
              trailing: true,
              size: 'xs',
              class: 'pe-7'
            },
            {
              trailing: true,
              size: 'sm',
              class: 'pe-8'
            },
            {
              trailing: true,
              size: 'md',
              class: 'pe-9'
            },
            {
              trailing: true,
              size: 'lg',
              class: 'pe-10'
            },
            {
              trailing: true,
              size: 'xl',
              class: 'pe-11'
            },
            {
              loading: true,
              leading: true,
              class: {
                leadingIcon: 'animate-spin'
              }
            },
            {
              loading: true,
              leading: false,
              trailing: true,
              class: {
                trailingIcon: 'animate-spin'
              }
            },
            {
              fixed: false,
              size: 'xs',
              class: 'md:text-xs'
            },
            {
              fixed: false,
              size: 'sm',
              class: 'md:text-xs'
            },
            {
              fixed: false,
              size: 'md',
              class: 'md:text-sm'
            },
            {
              fixed: false,
              size: 'lg',
              class: 'md:text-sm'
            }
          ],
          defaultVariants: {
            size: 'md',
            color: 'accent',
            variant: 'outline',
            position: 'popper'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitLab.

Changelog

No recent changes