Select
Displays a list of options for the user to pick from.
You can explore the select component in the following curated examples.
Async Select
Render a Select that fetches items asynchronously.
Select Tabs
Render a Tabs component within a Select.
Virtualized Select
Render a Select that virtualizes its options.
Anatomy
To set up the select correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-partattribute to help identify them in the DOM.
Examples
Learn how to use the Select component in your project. Let's take a look at the most basic example:
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const Basic = () => {
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
return (
<Select.Root collection={collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { Index, Portal } from 'solid-js/web'
export const Basic = () => {
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
return (
<Select.Root collection={collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>▼</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item()}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
</script>
<template>
<Select.Root :collection="collection">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const collection = createListCollection({ items: ['React', 'Solid', 'Svelte', 'Vue'] })
</script>
<Select.Root {collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item}
<Select.Item {item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
Controlled Value
Use the value and onValueChange props to control the selected items.
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
import { useState } from 'react'
interface Item {
label: string
value: string
disabled?: boolean | undefined
}
export const Controlled = () => {
const [_, setSelectedItems] = useState<Item[]>([])
const collection = createListCollection<Item>({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
return (
<Select.Root collection={collection} onValueChange={(e) => setSelectedItems(e.items)}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item.value} item={item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { createSignal } from 'solid-js'
import { Index, Portal } from 'solid-js/web'
interface Item {
label: string
value: string
disabled?: boolean
}
export const Controlled = () => {
const [, setSelectedItems] = createSignal<Item[]>([])
const collection = createListCollection<Item>({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
return (
<Select.Root collection={collection} onValueChange={(e) => setSelectedItems(e.items)}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item().label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
import { ref } from 'vue'
const collection = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
const value = ref(['vue'])
</script>
<template>
<Select.Root :collection="collection" v-model="value">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item.value" :item="item">
<Select.ItemText>{{ item.label }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
interface Item {
label: string
value: string
disabled?: boolean
}
let value = $state<string[]>([])
const collection = createListCollection<Item>({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
let selectedItems = $derived.by<Item[]>(() => {
return collection.items.filter((item) => value.includes(item.value))
})
</script>
<Select.Root {collection} bind:value>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item (item.value)}
<Select.Item {item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
{#if selectedItems.length > 0}
<div style="margin-top: 1rem;">
<p>Selected: {selectedItems.map((item) => item.label).join(', ')}</p>
</div>
{/if}
Grouping
Grouping related options can be useful for organizing options into categories.
- Use the
groupByprop to configure the grouping of the items. - Use the
collection.group()method to get the grouped items. - Use the
Select.ItemGroupandSelect.ItemGroupLabelcomponents to render the grouped items.
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const Grouping = () => {
const collection = createListCollection({
items: [
{ label: 'React', value: 'react', type: 'JS' },
{ label: 'Solid', value: 'solid', type: 'JS' },
{ label: 'Vue', value: 'vue', type: 'JS' },
{ label: 'Panda', value: 'panda', type: 'CSS' },
{ label: 'Tailwind', value: 'tailwind', type: 'CSS' },
],
groupBy: (item) => item.type,
})
return (
<Select.Root collection={collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
{collection.group().map(([type, group]) => (
<Select.ItemGroup key={type}>
<Select.ItemGroupLabel>{type}</Select.ItemGroupLabel>
{group.map((item) => (
<Select.Item key={item.value} item={item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
))}
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { For, Portal } from 'solid-js/web'
export const Grouping = () => {
const collection = createListCollection({
items: [
{ label: 'React', value: 'react', type: 'JS' },
{ label: 'Solid', value: 'solid', type: 'JS' },
{ label: 'Vue', value: 'vue', type: 'JS' },
{ label: 'Panda', value: 'panda', type: 'CSS' },
{ label: 'Tailwind', value: 'tailwind', type: 'CSS' },
],
groupBy: (item) => item.type,
})
return (
<Select.Root collection={collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<For each={collection.group()}>
{([type, group]) => (
<Select.ItemGroup>
<Select.ItemGroupLabel>{type}</Select.ItemGroupLabel>
<For each={group}>
{(item) => (
<Select.Item item={item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</For>
</Select.ItemGroup>
)}
</For>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: [
{ label: 'React', value: 'react', type: 'JS' },
{ label: 'Solid', value: 'solid', type: 'JS' },
{ label: 'Vue', value: 'vue', type: 'JS' },
{ label: 'Panda', value: 'panda', type: 'CSS' },
{ label: 'Tailwind', value: 'tailwind', type: 'CSS' },
],
groupBy: (item) => item.type,
})
</script>
<template>
<Select.Root :collection="collection">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup v-for="[type, group] in collection.group()" :key="type">
<Select.ItemGroupLabel>{{ type }}</Select.ItemGroupLabel>
<Select.Item v-for="item in group" :key="item.value" :item="item">
<Select.ItemText>{{ item.label }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const collection = createListCollection({
items: [
{ label: 'React', value: 'react', type: 'JS' },
{ label: 'Solid', value: 'solid', type: 'JS' },
{ label: 'Vue', value: 'vue', type: 'JS' },
{ label: 'Panda', value: 'panda', type: 'CSS' },
{ label: 'Tailwind', value: 'tailwind', type: 'CSS' },
],
groupBy: (item) => item.type,
})
</script>
<Select.Root {collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
{#each collection.group() as [type, group]}
<Select.ItemGroup>
<Select.ItemGroupLabel>{type}</Select.ItemGroupLabel>
{#each group as item (item.value)}
<Select.Item {item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
{/each}
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
Multiple Selection
To enable multiple item selection:
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const Multiple = () => {
const collection = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
return (
<Select.Root collection={collection} multiple>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item.value} item={item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { ChevronDownIcon } from 'lucide-solid'
import { Index, Portal } from 'solid-js/web'
export const Multiple = () => {
const collection = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
return (
<Select.Root collection={collection} multiple>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item().label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
</script>
<template>
<Select.Root :collection="collection" multiple>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item.value" :item="item">
<Select.ItemText>{{ item.label }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const collection = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
],
})
</script>
<Select.Root {collection} multiple>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item (item.value)}
<Select.Item {item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
Form Library
Here's an example of integrating the Select component with a form library.
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
import { Controller, type SubmitHandler, useForm } from 'react-hook-form'
interface Inputs {
framework: string
}
export const FormLibrary = () => {
const { control, handleSubmit } = useForm<Inputs>({
defaultValues: { framework: 'React' },
})
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const onSubmit: SubmitHandler<Inputs> = (data) => {
window.alert(JSON.stringify(data))
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="framework"
control={control}
render={({ field }) => (
<Select.Root
collection={collection}
value={field.value ? [field.value] : []}
onValueChange={(e) => field.onChange(e.value[0])}
name={field.name}
onInteractOutside={() => field.onBlur()}
>
<Select.Label>Framework</Select.Label>
<Select.HiddenSelect />
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Select.Root>
)}
/>
<button type="submit">Submit</button>
</form>
)
}
import { Select, createListCollection } from '@ark-ui/solid'
import { createForm, getValue, setValue } from '@modular-forms/solid'
import { createMemo } from 'solid-js'
import { Index, Portal } from 'solid-js/web'
export const FormLibrary = () => {
const frameworks = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
],
})
const [formStore, { Form, Field }] = createForm({
initialValues: { value: 'solid' },
})
const value = createMemo(() => getValue(formStore, 'value'))
return (
<>
<div>Value is {value()}</div>
<Form
onSubmit={(e) => {
console.log(e.value)
}}
>
<Field name="value">
{(field, props) => (
<Select.Root
collection={frameworks}
value={field.value ? [field.value] : []}
invalid={!!field.error}
name={field.name}
onValueChange={(e) => {
setValue(formStore, field.name, e.value[0])
}}
>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={frameworks.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item().label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect {...props} />
</Select.Root>
)}
</Field>
<button type="submit">Submit</button>
</Form>
</>
)
}
<script setup lang="ts">
import { Select, createListCollection } from '@ark-ui/vue/select'
import { useForm, useField } from 'vee-validate'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const { handleSubmit, values } = useForm({
initialValues: {
framework: 'Vue',
},
})
const { value: framework, setValue } = useField<string>('framework')
const onSubmit = handleSubmit((values) => {
window.alert(JSON.stringify(values))
})
</script>
<template>
<div>
<div>Value is {{ values.framework }}</div>
<form @submit="onSubmit">
<Select.Root
:collection="collection"
:model-value="framework ? [framework] : []"
@value-change="(e) => setValue(e.value[0])"
>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect name="framework" />
</Select.Root>
<button type="submit">Submit</button>
</form>
</div>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { createForm } from '@tanstack/svelte-form'
const frameworks = createListCollection({
items: [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
],
})
const form = createForm(() => ({
defaultValues: {
framework: 'solid',
},
onSubmit: async ({ value }) => {
console.log(value)
},
}))
const formData = $derived(form.state.values)
</script>
<div>Value is {formData.framework}</div>
<form
onsubmit={(e) => {
e.preventDefault()
form.handleSubmit()
}}
>
<form.Field name="framework">
{#snippet children(field)}
{@const state = field.state}
<Select.Root
collection={frameworks}
value={state.value ? [state.value] : []}
invalid={state.meta.errors.length > 0}
name={field.name}
onValueChange={(details) => {
field.handleChange(details.value[0])
}}
>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each frameworks.items as item}
<Select.Item {item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
{/snippet}
</form.Field>
<button type="submit">Submit</button>
</form>
Field Component
The Field component helps manage form-related state and accessibility attributes of a select. It includes handling
ARIA labels, helper text, and error text to ensure proper accessibility.
import { Field } from '@ark-ui/react/field'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const WithField = (props: Field.RootProps) => {
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
return (
<Field.Root {...props}>
<Select.Root collection={collection}>
<Select.Label>Label</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
</Select.Control>
<Select.Positioner>
<Select.Content>
{collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.Content>
</Select.Positioner>
<Select.HiddenSelect />
</Select.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
}
import { Field } from '@ark-ui/solid/field'
import { Select, createListCollection } from '@ark-ui/solid/select'
import { ChevronDownIcon } from 'lucide-solid'
import { Index } from 'solid-js/web'
export const WithField = (props: Field.RootProps) => {
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
return (
<Field.Root {...props}>
<Select.Root collection={collection}>
<Select.Label>Label</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
</Select.Control>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item()}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
<Select.HiddenSelect />
</Select.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
}
<script setup lang="ts">
import { Field, type FieldRootProps } from '@ark-ui/vue/field'
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const props = defineProps<FieldRootProps>()
</script>
<template>
<Field.Root v-bind="props">
<Select.Root :collection="collection">
<Select.Label>Label</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
<script lang="ts">
import { Field, type FieldRootProps } from '@ark-ui/svelte/field'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const props: FieldRootProps = $props()
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
</script>
<Field.Root {...props}>
<Select.Root {collection}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
</Select.Control>
<Select.Positioner>
<Select.Content>
{#each collection.items as item}
<Select.Item {item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.Content>
</Select.Positioner>
<Select.HiddenSelect />
</Select.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
Async Loading
Here's an example of how to load the items asynchronously when the select is opened.
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
import { useState } from 'react'
function loadData() {
return new Promise<string[]>((resolve) => {
setTimeout(() => resolve(['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Ember']), 500)
})
}
export const Async = () => {
const [items, setItems] = useState<string[] | null>(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<Error | null>(null)
const collection = createListCollection<string>({
items: items || [],
})
const handleOpenChange = (details: Select.OpenChangeDetails) => {
if (details.open && items == null) {
setLoading(true)
setError(null)
loadData()
.then((data) => setItems(data))
.catch((err) => setError(err))
.finally(() => setLoading(false))
}
}
return (
<Select.Root collection={collection} onOpenChange={handleOpenChange}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
{loading ? (
<div>Loading...</div>
) : error ? (
<div>Error: {error.message}</div>
) : (
collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))
)}
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { Index, Show, createMemo, createSignal } from 'solid-js'
import { Portal } from 'solid-js/web'
function loadData() {
return new Promise<string[]>((resolve) => {
setTimeout(() => resolve(['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Ember']), 500)
})
}
export const Async = () => {
const [items, setItems] = createSignal<string[] | null>(null)
const [loading, setLoading] = createSignal(false)
const [error, setError] = createSignal<Error | null>(null)
const collection = createMemo(() =>
createListCollection<string>({
items: items() || [],
}),
)
const handleOpenChange = (details: Select.OpenChangeDetails) => {
if (details.open && items() === null) {
setLoading(true)
setError(null)
loadData()
.then((data) => setItems(data))
.catch((err) => setError(err))
.finally(() => setLoading(false))
}
}
return (
<Select.Root collection={collection()} onOpenChange={handleOpenChange}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select" />
<Select.Indicator>▼</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Show when={loading()}>
<div>Loading...</div>
</Show>
<Show when={error()}>
<div>Error: {error()?.message}</div>
</Show>
<Show when={items() !== null && !loading() && !error()}>
<Index each={collection().items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item()}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Show>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
// biome-ignore lint/style/useImportType: intentional
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
import { computed, ref } from 'vue'
function loadData() {
return new Promise<string[]>((resolve) => {
setTimeout(() => resolve(['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Ember']), 500)
})
}
const data = ref<string[] | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const collection = computed(() =>
createListCollection({
items: data.value ?? [],
}),
)
const handleOpenChange = async (details: Select.OpenChangeDetails) => {
if (details.open && data.value === null) {
loading.value = true
error.value = null
try {
const result = await loadData()
data.value = result
} catch (err) {
error.value = err as Error
} finally {
loading.value = false
}
}
}
</script>
<template>
<Select.Root :collection="collection" @open-change="handleOpenChange">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error.message }}</div>
<template v-else>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</template>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
// biome-ignore lint/style/useImportType: intentional
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
function loadData() {
return new Promise<string[]>((resolve) => {
setTimeout(() => resolve(['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Ember']), 500)
})
}
let data = $state<string[] | null>(null)
let loading = $state(false)
let error = $state<Error | null>(null)
const collection = $derived(
createListCollection({
items: data ?? [],
}),
)
const handleOpenChange = (details: Select.OpenChangeDetails) => {
if (details.open && data === null) {
loading = true
error = null
loadData()
.then((result) => {
data = result
})
.catch((err) => {
error = err
})
.finally(() => {
loading = false
})
}
}
</script>
<Select.Root {collection} onOpenChange={handleOpenChange}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
{#if loading}
<div>Loading...</div>
{:else if error}
<div>Error: {error.message}</div>
{:else}
{#each collection.items as item}
<Select.Item {item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
{/if}
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
Root Provider
The RootProvider component provides a context for the select. It accepts the value of the useSelect hook. You can
leverage it to access the component state and methods from outside the select.
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection, useSelect } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const RootProvider = () => {
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
const select = useSelect({ collection: collection })
return (
<>
<button onClick={() => select.focus()}>Focus</button>
<Select.RootProvider value={select}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.RootProvider>
</>
)
}
import { Select, createListCollection, useSelect } from '@ark-ui/solid/select'
import { Index, Portal } from 'solid-js/web'
export const RootProvider = () => {
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
const select = useSelect({ collection: collection })
return (
<>
<button onClick={() => select().focus()}>Focus</button>
<Select.RootProvider value={select}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>▼</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item()}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.RootProvider>
</>
)
}
<script setup lang="ts">
import { Select, createListCollection, useSelect } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const select = useSelect({ collection })
</script>
<template>
<button @click="select.focus()">Focus</button>
<Select.RootProvider :value="select">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.RootProvider>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection, useSelect } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const collection = createListCollection({ items: ['React', 'Solid', 'Vue', 'Svelte'] })
const id = $props.id()
const select = useSelect({ collection, id })
</script>
<div>
<button onclick={() => select().focus()}>Focus</button>
<Select.RootProvider value={select}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item}
<Select.Item {item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.RootProvider>
</div>
If you're using the
RootProvidercomponent, you don't need to use theRootcomponent.
Select on Highlight
Here's an example of automatically selecting items when they are highlighted (hovered or navigated to with keyboard).
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection, useSelect } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const SelectOnHighlight = () => {
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const select = useSelect({
collection,
onHighlightChange({ highlightedValue }) {
if (highlightedValue) {
select.selectValue(highlightedValue)
}
},
})
return (
<Select.RootProvider value={select}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.RootProvider>
)
}
import { Select, createListCollection, useSelect } from '@ark-ui/solid/select'
import { Index, Portal } from 'solid-js/web'
export const SelectOnHighlight = () => {
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const select = useSelect({
collection,
onHighlightChange({ highlightedValue }) {
if (highlightedValue) {
select().selectValue(highlightedValue)
}
},
})
return (
<Select.RootProvider value={select}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>▼</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item()}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.RootProvider>
)
}
<script setup lang="ts">
import { Select, createListCollection, useSelect } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const select = useSelect({
collection,
onHighlightChange({ highlightedValue }) {
if (highlightedValue) {
select.value.selectValue(highlightedValue)
}
},
})
</script>
<template>
<Select.RootProvider :value="select">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.RootProvider>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection, useSelect } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte'],
})
const select = useSelect({
collection,
onHighlightChange({ highlightedValue }) {
if (highlightedValue) {
select().selectValue(highlightedValue)
}
},
})
</script>
<Select.RootProvider value={select}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item}
<Select.Item {item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.RootProvider>
Maximum Selected Items
Here's an example of limiting the number of items that can be selected in a multiple select.
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
import { useState } from 'react'
const items = ['React', 'Solid', 'Vue', 'Svelte']
export const MaxSelected = () => {
const [value, setValue] = useState<string[]>([])
const maxSelected = 2
const hasReachedMax = (value: string[]) => value.length >= maxSelected
const collection = createListCollection({
items: items.map((item) => ({
label: item,
value: item,
disabled: hasReachedMax(value) && !value.includes(item),
})),
})
const handleValueChange = (details: Select.ValueChangeDetails) => {
if (hasReachedMax(value) && details.value.length) return
setValue(details.value)
}
return (
<Select.Root collection={collection} multiple value={value} onValueChange={handleValueChange}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item.value} item={item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { Index, createMemo, createSignal } from 'solid-js'
import { Portal } from 'solid-js/web'
const items = ['React', 'Solid', 'Vue', 'Svelte']
export const MaxSelected = () => {
const [value, setValue] = createSignal<string[]>([])
const maxSelected = 2
const hasReachedMax = (value: string[]) => value.length >= maxSelected
const collection = createMemo(() =>
createListCollection({
items: items.map((item) => ({
label: item,
value: item,
disabled: hasReachedMax(value()) && !value().includes(item),
})),
}),
)
const handleValueChange = (details: Select.ValueChangeDetails) => {
if (hasReachedMax(value()) && details.value.length) return
setValue(details.value)
}
return (
<Select.Root collection={collection()} multiple value={value()} onValueChange={handleValueChange}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>▼</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection().items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item().label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
// biome-ignore lint/style/useImportType: intentional
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
import { computed, ref } from 'vue'
const items = ['React', 'Solid', 'Vue', 'Svelte']
const value = ref<string[]>([])
const maxSelected = 2
const hasReachedMax = (value: string[]) => value.length >= maxSelected
const collection = computed(() =>
createListCollection({
items: items.map((item) => ({
label: item,
value: item,
disabled: hasReachedMax(value.value) && !value.value.includes(item),
})),
}),
)
const handleValueChange = (details: Select.ValueChangeDetails) => {
if (hasReachedMax(value.value) && details.value.length) return
value.value = details.value
}
</script>
<template>
<Select.Root :collection="collection" multiple :value="value" @value-change="handleValueChange">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item.value" :item="item">
<Select.ItemText>{{ item.label }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
// biome-ignore lint/style/useImportType: intentional
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const items = ['React', 'Solid', 'Vue', 'Svelte']
let value = $state<string[]>([])
const maxSelected = 2
const hasReachedMax = (value: string[]) => value.length >= maxSelected
const collection = $derived(
createListCollection({
items: items.map((item) => ({
label: item,
value: item,
disabled: hasReachedMax(value) && !value.includes(item),
})),
}),
)
const handleValueChange = (details: Select.ValueChangeDetails) => {
if (hasReachedMax(value) && details.value.length) return
value = details.value
}
</script>
<Select.Root {collection} multiple {value} onValueChange={handleValueChange}>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item (item.value)}
<Select.Item {item}>
<Select.ItemText>{item.label}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
Lazy Mount
The lazyMount and unmountOnExit props allow you to control when the select content is mounted and unmounted from the
DOM. This can improve performance by only rendering the content when needed.
import { Portal } from '@ark-ui/react/portal'
import { Select, createListCollection } from '@ark-ui/react/select'
import { ChevronDownIcon } from 'lucide-react'
export const LazyMount = () => {
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Alpine'],
})
return (
<Select.Root collection={collection} lazyMount unmountOnExit>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{collection.items.map((item) => (
<Select.Item key={item} item={item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
))}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
import { Select, createListCollection } from '@ark-ui/solid/select'
import { Index, Portal } from 'solid-js/web'
export const LazyMount = () => {
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Alpine'],
})
return (
<Select.Root collection={collection} lazyMount unmountOnExit>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>▼</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Index each={collection.items}>
{(item) => (
<Select.Item item={item()}>
<Select.ItemText>{item()}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
)}
</Index>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
)
}
<script setup lang="ts">
import { Select, createListCollection } from '@ark-ui/vue/select'
import { ChevronDownIcon } from 'lucide-vue-next'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Alpine'],
})
</script>
<template>
<Select.Root :collection="collection" :lazy-mount="true" :unmount-on-exit="true">
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in collection.items" :key="item" :item="item">
<Select.ItemText>{{ item }}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Teleport>
<Select.HiddenSelect />
</Select.Root>
</template>
<script lang="ts">
import { Portal } from '@ark-ui/svelte/portal'
import { Select, createListCollection } from '@ark-ui/svelte/select'
import { ChevronDownIcon } from 'lucide-svelte'
const collection = createListCollection({
items: ['React', 'Solid', 'Vue', 'Svelte', 'Angular', 'Alpine', 'Ember'],
})
</script>
<Select.Root {collection} lazyMount unmountOnExit>
<Select.Label>Framework</Select.Label>
<Select.Control>
<Select.Trigger>
<Select.ValueText placeholder="Select a Framework" />
<Select.Indicator>
<ChevronDownIcon />
</Select.Indicator>
</Select.Trigger>
<Select.ClearTrigger>Clear</Select.ClearTrigger>
</Select.Control>
<Portal>
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
{#each collection.items as item}
<Select.Item {item}>
<Select.ItemText>{item}</Select.ItemText>
<Select.ItemIndicator>✓</Select.ItemIndicator>
</Select.Item>
{/each}
</Select.ItemGroup>
</Select.Content>
</Select.Positioner>
</Portal>
<Select.HiddenSelect />
</Select.Root>
Guides
Type-Safety
The Select.RootComponent type enables you to create closed, strongly typed wrapper components that maintain full type
safety for collection items.
This is particularly useful when building reusable select components with custom props and consistent styling.
import { Select as ArkSelect, type CollectionItem } from '@ark-ui/react/select'
import { createListCollection } from '@ark-ui/react/collection'
interface SelectProps<T extends CollectionItem> extends ArkSelect.RootProps<T> {}
const Select: ArkSelect.RootComponent = (props) => {
return <ArkSelect.Root {...props}>{/* ... */}</ArkSelect.Root>
}
Then, you can use the Select component as follows:
const App = () => {
const collection = createListCollection({
initialItems: [
{ label: 'React', value: 'react' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte' },
],
})
return (
<Select
collection={collection}
onValueChange={(e) => {
// this will be strongly typed Array<{ label: string, value: string }>
console.log(e.items)
}}
>
{/* ... */}
</Select>
)
}
Usage in Popover or Dialog
When using the Select component within a Popover or Dialog, avoid rendering its content within a Portal or
Teleport.
This ensures the Select's content stays within the Popover/Dialog's DOM hierarchy rather than being portalled to the document body, maintaining proper interaction and accessibility behavior.
Hidden Select
The Select.HiddenSelect component renders a native HTML <select> element that's visually hidden but remains in the
DOM. This component is essential for:
- Form submission: Native form submission and serialization work seamlessly since the actual
<select>element exists in the DOM - Browser auto-fill: Browsers can properly auto-fill the select based on previously submitted form data
- Progressive enhancement: Forms remain functional even if JavaScript fails to load
<Select.Root>
<Select.HiddenSelect />
{/* Other Select components */}
</Select.Root>
The hidden select automatically syncs with the Select component's value, ensuring form data is always up-to-date.
Empty State
You can create an empty state component that displays when there are no items in the collection. Use the
useSelectContext hook to check the collection size:
const SelectEmpty = (props: React.ComponentProps<'div'>) => {
const select = useSelectContext()
if (select.collection.size === 0) {
return <div {...props} role="presentation" />
}
return null
}
Then use it within your Select content:
<Select.Content>
<SelectEmpty>No items to display</SelectEmpty>
{/* Your items */}
</Select.Content>
Available height and width
The following css variables are exposed to the Select.Positioner which you can use to style the Select.Content
/* width of the select trigger */
--reference-width: <pixel-value>;
/* width of the available viewport */
--available-width: <pixel-value>;
/* height of the available viewport */
--available-height: <pixel-value>;
For example, if you want to make sure the maximum height doesn't exceed the available height, you can use the following:
[data-scope='select'][data-part='content'] {
max-height: calc(var(--available-height) - 100px);
}
API Reference
Root
| Prop | Default | Type |
|---|---|---|
collection | ListCollection<T>The collection of items | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
closeOnSelect | true | booleanWhether the select should close after an item is selected |
composite | true | booleanWhether the select is a composed with other composite widgets like tabs or combobox |
defaultHighlightedValue | stringThe initial value of the highlighted item when opened. Use when you don't need to control the highlighted value of the select. | |
defaultOpen | booleanWhether the select's open state is controlled by the user | |
defaultValue | string[]The initial default value of the select when rendered. Use when you don't need to control the value of the select. | |
deselectable | booleanWhether the value can be cleared by clicking the selected item. **Note:** this is only applicable for single selection | |
disabled | booleanWhether the select is disabled | |
form | stringThe associate form of the underlying select. | |
highlightedValue | stringThe controlled key of the highlighted item | |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
root: string
content: string
control: string
trigger: string
clearTrigger: string
label: string
hiddenSelect: string
positioner: string
item: (id: string | number) => string
itemGroup: (id: string | number) => string
itemGroupLabel: (id: string | number) => string
}>The ids of the elements in the select. Useful for composition. | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
invalid | booleanWhether the select is invalid | |
lazyMount | false | booleanWhether to enable lazy mounting |
loopFocus | false | booleanWhether to loop the keyboard navigation through the options |
multiple | booleanWhether to allow multiple selection | |
name | stringThe `name` attribute of the underlying select. | |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
onFocusOutside | (event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component | |
onHighlightChange | (details: HighlightChangeDetails<T>) => voidThe callback fired when the highlighted item changes. | |
onInteractOutside | (event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component | |
onOpenChange | (details: OpenChangeDetails) => voidFunction called when the popup is opened | |
onPointerDownOutside | (event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component | |
onSelect | (details: SelectionDetails) => voidFunction called when an item is selected | |
onValueChange | (details: ValueChangeDetails<T>) => voidThe callback fired when the selected item changes. | |
open | booleanWhether the select menu is open | |
positioning | PositioningOptionsThe positioning options of the menu. | |
present | booleanWhether the node is present (controlled by the user) | |
readOnly | booleanWhether the select is read-only | |
required | booleanWhether the select is required | |
scrollToIndexFn | (details: ScrollToIndexDetails) => voidFunction to scroll to a specific index | |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
unmountOnExit | false | booleanWhether to unmount on exit. |
value | string[]The controlled keys of the selected items |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | root |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
ClearTrigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | clear-trigger |
[data-invalid] | Present when invalid |
Content
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--layer-index | The index of the dismissable in the layer stack |
--nested-layer-count | The number of nested selects |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | content |
[data-state] | "open" | "closed" |
[data-nested] | listbox |
[data-has-nested] | listbox |
[data-placement] | The placement of the content |
[data-activedescendant] | The id the active descendant of the content |
Control
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | control |
[data-state] | "open" | "closed" |
[data-focus] | Present when focused |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
HiddenSelect
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Indicator
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | indicator |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
ItemGroupLabel
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
ItemGroup
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | item-group |
[data-disabled] | Present when disabled |
ItemIndicator
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | item-indicator |
[data-state] | "checked" | "unchecked" |
Item
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
item | anyThe item to render | |
persistFocus | booleanWhether hovering outside should clear the highlighted state |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | item |
[data-value] | The value of the item |
[data-state] | "checked" | "unchecked" |
[data-highlighted] | Present when highlighted |
[data-disabled] | Present when disabled |
ItemText
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | item-text |
[data-state] | "checked" | "unchecked" |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
Label
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | label |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
List
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Positioner
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--transform-origin | The transform origin for animations |
--reference-width | The width of the reference element |
--available-width | The available width in viewport |
--available-height | The available height in viewport |
--x | The x position for transform |
--y | The y position for transform |
--z-index | The z-index value |
--reference-height | The height of the root |
RootProvider
| Prop | Default | Type |
|---|---|---|
value | UseSelectReturn<T> | |
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | booleanWhether to enable lazy mounting |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
present | booleanWhether the node is present (controlled by the user) | |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
unmountOnExit | false | booleanWhether to unmount on exit. |
Trigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | trigger |
[data-state] | "open" | "closed" |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
[data-placement] | The placement of the trigger |
[data-placeholder-shown] | Present when placeholder is shown |
ValueText
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
placeholder | stringText to display when no value is selected. |
| Data Attribute | Value |
|---|---|
[data-scope] | select |
[data-part] | value-text |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-focus] | Present when focused |
Accessibility
Complies with the Listbox WAI-ARIA design pattern.
Keyboard Support
| Key | Description |
|---|---|
Space | When focus is on trigger, opens the select and focuses the first selected item. When focus is on the content, selects the highlighted item. |
Enter | When focus is on trigger, opens the select and focuses the first selected item. When focus is on content, selects the focused item. |
ArrowDown | When focus is on trigger, opens the select. When focus is on content, moves focus to the next item. |
ArrowUp | When focus is on trigger, opens the select. When focus is on content, moves focus to the previous item. |
Esc | Closes the select and moves focus to trigger. |
A-Za-z | When focus is on trigger, selects the item whose label starts with the typed character. When focus is on the listbox, moves focus to the next item with a label that starts with the typed character. |