Usage
Use the v-model
directive to control the value of the Select or the default-value
prop to set the initial value when you do not need to control its state.
Items
Use the items
prop as an array of strings, numbers or booleans:
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" :items="items" class="w-48" />
</template>
You can also pass an array of objects with the following properties:
label?: string
value?: string
type?: "label" | "separator" | "item"
icon?: string
avatar?: AvatarProps
chip?: ChipProps
disabled?: boolean
<script setup lang="ts">
const items = ref([
{
label: 'Backlog',
value: 'backlog'
},
{
label: 'Todo',
value: 'todo'
},
{
label: 'In Progress',
value: 'in_progress'
},
{
label: 'Done',
value: 'done'
}
])
const value = ref('backlog')
</script>
<template>
<USelect v-model="value" :items="items" class="w-48" />
</template>
value
property of the object in the v-model
directive or the default-value
prop.You can also pass an array of arrays to the items
prop to display separated groups of items.
<script setup lang="ts">
const items = ref([
['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'],
['Aubergine', 'Broccoli', 'Carrot', 'Courgette', 'Leek']
])
const value = ref('Apple')
</script>
<template>
<USelect v-model="value" :items="items" class="w-48" />
</template>
Value Key
You can change the property that is used to set the value by using the value-key
prop. Defaults to value
.
<script setup lang="ts">
const items = ref([
{
label: 'Backlog',
id: 'backlog'
},
{
label: 'Todo',
id: 'todo'
},
{
label: 'In Progress',
id: 'in_progress'
},
{
label: 'Done',
id: 'done'
}
])
const value = ref('backlog')
</script>
<template>
<USelect v-model="value" value-key="id" :items="items" class="w-48" />
</template>
Placeholder
Use the placeholder
prop to set a placeholder text.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
</script>
<template>
<USelect placeholder="Select status" :items="items" class="w-48" />
</template>
Content
Use the content
prop to control how the Select content is rendered, like its align
, side
or position
for example.
content.align
, content.side
, etc. properties only apply when content.position
is set to popper
.<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Todo')
</script>
<template>
<USelect
v-model="value"
:content="{
position: 'item-aligned',
align: 'center',
side: 'bottom',
sideOffset: 8
}"
:items="items"
class="w-48"
/>
</template>
Color
Use the color
prop to change the ring color when the Select is focused.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" color="neutral" highlight :items="items" class="w-48" />
</template>
highlight
prop is used here to show the focus state. It's used internally when a validation error occurs.Variant
Use the variant
prop to change the variant of the Select.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" color="neutral" variant="subtle" :items="items" class="w-48" />
</template>
Size
Use the size
prop to change the size of the Select.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" size="xl" :items="items" class="w-48" />
</template>
Icon
Use the icon
prop to show an Icon inside the Select.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" icon="i-lucide-search" :items="items" class="w-48" />
</template>
Trailing Icon
Use the trailing-icon
prop to customize the trailing Icon. Defaults to i-lucide-chevron-down
.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" trailing-icon="i-lucide-arrow-down" :items="items" class="w-48" />
</template>
Selected Icon
Use the selected-icon
prop to customize the icon when an item is selected. Defaults to i-lucide-check
.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" selected-icon="i-lucide-flame" :items="items" class="w-48" />
</template>
Avatar
Use the avatar
prop to show an Avatar inside the Select.
<script setup lang="ts">
const items = ref(['Nuxt', 'NuxtHub', 'NuxtLabs', 'Nuxt Modules', 'Nuxt Community'])
const value = ref('Nuxt')
</script>
<template>
<USelect
v-model="value"
:avatar="{
src: 'https://github.com/nuxt.png'
}"
:items="items"
class="w-48"
/>
</template>
Loading
Use the loading
prop to show a loading icon on the Select.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" loading :items="items" class="w-48" />
</template>
Loading Icon
Use the loading-icon
prop to customize the loading icon. Defaults to i-lucide-refresh-ccw
.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect v-model="value" loading loading-icon="i-lucide-repeat-2" :items="items" class="w-48" />
</template>
Disabled
Use the disabled
prop to disable the Select.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
</script>
<template>
<USelect disabled placeholder="Select status" :items="items" class="w-48" />
</template>
Examples
With items type
You can use the type
property with separator
to display a separator between items or label
to display a label.
<script setup lang="ts">
const items = ref([
{
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>
<USelect v-model="value" :items="items" class="w-48" />
</template>
With icons in items
You can use the icon
property to display an Icon inside the items.
<script setup lang="ts">
const items = ref([
{
label: 'Backlog',
value: 'backlog',
icon: 'i-lucide-circle-help'
},
{
label: 'Todo',
value: 'todo',
icon: 'i-lucide-circle-plus'
},
{
label: 'In Progress',
value: 'in_progress',
icon: 'i-lucide-circle-arrow-up'
},
{
label: 'Done',
value: 'done',
icon: 'i-lucide-circle-check'
}
])
const value = ref(items.value[0]?.value)
const icon = computed(() => items.value.find(item => item.value === value.value)?.icon)
</script>
<template>
<USelect v-model="value" :icon="icon" :items="items" class="w-48" />
</template>
value
property of the selected item.#leading
slot to display the selected icon.With avatar in items
You can use the avatar
property to display an Avatar inside the items.
<script setup lang="ts">
const items = ref([
{
label: 'benjamincanac',
value: 'benjamincanac',
avatar: {
src: 'https://github.com/benjamincanac.png',
alt: 'benjamincanac'
}
},
{
label: 'romhml',
value: 'romhml',
avatar: {
src: 'https://github.com/romhml.png',
alt: 'romhml'
}
},
{
label: 'noook',
value: 'noook',
avatar: {
src: 'https://github.com/noook.png',
alt: 'noook'
}
}
])
const value = ref(items.value[0]?.value)
const avatar = computed(() => items.value.find(item => item.value === value.value)?.avatar)
</script>
<template>
<USelect v-model="value" :avatar="avatar" :items="items" class="w-48" />
</template>
value
property of the selected item.#leading
slot to display the selected avatar.With chip in items
You can use the chip
property to display a Chip inside the items.
<script setup lang="ts">
const items = ref([
{
label: 'bug',
value: 'bug',
chip: {
color: 'error' as const
}
},
{
label: 'feature',
value: 'feature',
chip: {
color: 'success' as const
}
},
{
label: 'enhancement',
value: 'enhancement',
chip: {
color: 'info' as const
}
}
])
const value = ref(items.value[0]?.value)
function getChip(value: string) {
return items.value.find(item => item.value === value)?.chip
}
</script>
<template>
<USelect v-model="value" :items="items" class="w-48">
<template #leading="{ modelValue, ui }">
<UChip
v-if="modelValue"
v-bind="getChip(modelValue)"
inset
standalone
:size="ui.itemLeadingChipSize()"
:class="ui.itemLeadingChip()"
/>
</template>
</USelect>
</template>
#leading
slot is used to display the selected chip.Control open state
You can control the open state by using the default-open
prop or the v-model:open
directive.
<script setup lang="ts">
const open = ref(false)
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
defineShortcuts({
o: () => open.value = !open.value
})
</script>
<template>
<USelect v-model="value" v-model:open="open" :items="items" class="w-48" />
</template>
With rotating icon
Here is an example with a rotating icon that indicates the open state of the Select.
<script setup lang="ts">
const items = ref(['Backlog', 'Todo', 'In Progress', 'Done'])
const value = ref('Backlog')
</script>
<template>
<USelect
v-model="value"
:items="items"
:ui="{
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
}"
class="w-48"
/>
</template>
With fetched items
You can fetch items from an API and use them in the Select.
<script setup lang="ts">
const { data: users, status } = await useFetch('https://jsonplaceholder.typicode.com/users', {
transform: (data: { id: number, name: string }[]) => {
return data?.map(user => ({
label: user.name,
value: String(user.id),
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
})) || []
},
lazy: true
})
function getUserAvatar(value: string) {
return users.value?.find(user => user.value === value)?.avatar || {}
}
</script>
<template>
<USelect
:items="users || []"
:loading="status === 'pending'"
icon="i-lucide-user"
placeholder="Select user"
class="w-48"
>
<template #leading="{ modelValue, ui }">
<UAvatar
v-if="modelValue"
v-bind="getUserAvatar(modelValue)"
:size="ui.leadingAvatarSize()"
:class="ui.leadingAvatar()"
/>
</template>
</USelect>
</template>
API
Props
Prop | Default | Type |
---|---|---|
id |
| |
placeholder |
The placeholder text when the select is empty. | |
color |
|
|
variant |
|
|
size |
|
|
trailingIcon |
|
The icon displayed to open the menu. |
selectedIcon |
|
The icon displayed when an item is selected. |
content |
|
The content of the menu. |
arrow |
|
Display an arrow alongside the menu. |
portal |
|
Render the menu in a portal. |
valueKey |
|
When |
labelKey |
|
When |
items |
| |
highlight |
Highlight the ring color like a focus state. | |
modelValue |
The controlled value of the Select. Can be bind as | |
defaultValue |
The value of the select when initially rendered. Use when you do not need to control the state of the Select | |
disabled |
When | |
open |
The controlled open state of the Select. Can be bind as | |
defaultOpen |
The open state of the select when it is initially rendered. Use when you do not need to control its open state. | |
name |
The name of the Select. Submitted with its owning form as part of a name/value pair. | |
required |
When | |
autocomplete |
Native html input | |
icon |
Display an icon based on the | |
avatar |
Display an avatar on the left side. | |
leading |
When | |
leadingIcon |
Display an icon on the left side. | |
trailing |
When | |
loading |
When | |
loadingIcon |
|
The icon when the |
ui |
|
Slots
Slot | Type |
---|---|
leading |
|
trailing |
|
item |
|
item-leading |
|
item-label |
|
item-trailing |
|
Emits
Event | Type |
---|---|
blur |
|
change |
|
focus |
|
update:modelValue |
|
update:open |
|
Theme
export default defineAppConfig({
ui: {
select: {
slots: {
base: [
'relative group rounded-[calc(var(--ui-radius)*1.5)] inline-flex items-center focus:outline-none disabled:cursor-not-allowed disabled:opacity-75',
'transition-colors'
],
leading: 'absolute inset-y-0 start-0 flex items-center',
leadingIcon: 'shrink-0 text-[var(--ui-text-dimmed)]',
leadingAvatar: 'shrink-0',
leadingAvatarSize: '',
trailing: 'absolute inset-y-0 end-0 flex items-center',
trailingIcon: 'shrink-0 text-[var(--ui-text-dimmed)]',
value: 'truncate group-data-placeholder:text-[var(--ui-text-dimmed)]',
arrow: 'fill-[var(--ui-border)]',
content: 'max-h-60 w-[var(--radix-popper-anchor-width)] bg-[var(--ui-bg)] shadow-lg rounded-[calc(var(--ui-radius)*1.5)] ring ring-[var(--ui-border)] overflow-hidden data-[state=open]:animate-[scale-in_100ms_ease-out] data-[state=closed]:animate-[scale-out_100ms_ease-in]',
viewport: 'divide-y divide-[var(--ui-border)] scroll-py-1',
group: 'p-1 isolate',
empty: 'py-2 text-center text-sm text-[var(--ui-text-muted)]',
label: 'font-semibold text-[var(--ui-text-highlighted)]',
separator: '-mx-1 my-1 h-px bg-[var(--ui-border)]',
item: [
'group relative w-full flex items-center select-none outline-none before:absolute before:z-[-1] before:inset-px before:rounded-[calc(var(--ui-radius)*1.5)] data-disabled:cursor-not-allowed data-disabled:opacity-75 text-[var(--ui-text)] data-highlighted:text-[var(--ui-text-highlighted)] data-highlighted:before:bg-[var(--ui-bg-elevated)]/50',
'transition-colors before:transition-colors'
],
itemLeadingIcon: [
'shrink-0 text-[var(--ui-text-dimmed)] group-data-highlighted:text-[var(--ui-text)]',
'transition-colors'
],
itemLeadingAvatar: 'shrink-0',
itemLeadingAvatarSize: '',
itemLeadingChip: 'shrink-0',
itemLeadingChipSize: '',
itemTrailing: 'ms-auto inline-flex gap-1.5 items-center',
itemTrailingIcon: 'shrink-0',
itemLabel: 'truncate'
},
variants: {
buttonGroup: {
horizontal: 'not-only:first:rounded-e-none not-only:last:rounded-s-none not-last:not-first:rounded-none',
vertical: 'not-only:first:rounded-b-none not-only:last:rounded-t-none not-last:not-first:rounded-none'
},
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'
},
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'
},
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'
},
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'
},
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'
}
},
variant: {
outline: 'text-[var(--ui-text-highlighted)] bg-[var(--ui-bg)] ring ring-inset ring-[var(--ui-border-accented)]',
soft: 'text-[var(--ui-text-highlighted)] bg-[var(--ui-bg-elevated)]/50 hover:bg-[var(--ui-bg-elevated)] focus:bg-[var(--ui-bg-elevated)] disabled:bg-[var(--ui-bg-elevated)]/50',
subtle: 'text-[var(--ui-text-highlighted)] bg-[var(--ui-bg-elevated)] ring ring-inset ring-[var(--ui-border-accented)]',
ghost: 'text-[var(--ui-text-highlighted)] bg-transparent hover:bg-[var(--ui-bg-elevated)] focus:bg-[var(--ui-bg-elevated)] disabled:bg-transparent dark:disabled:bg-transparent',
none: 'text-[var(--ui-text-highlighted)] bg-transparent'
},
color: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
},
leading: {
true: ''
},
trailing: {
true: ''
},
loading: {
true: ''
},
highlight: {
true: ''
},
type: {
file: 'file:me-1.5 file:font-medium file:text-[var(--ui-text-muted)] file:outline-none'
}
},
compoundVariants: [
{
color: 'primary',
variant: [
'outline',
'subtle'
],
class: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--ui-primary)]'
},
{
color: 'primary',
highlight: true,
class: 'ring ring-inset ring-[var(--ui-primary)]'
},
{
color: 'neutral',
variant: [
'outline',
'subtle'
],
class: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--ui-border-inverted)]'
},
{
color: 'neutral',
highlight: true,
class: 'ring ring-inset ring-[var(--ui-border-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'
}
}
],
defaultVariants: {
size: 'md',
color: 'primary',
variant: 'outline'
}
}
}
})