Select
Displays a list of options for the user to pick from.
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-part
attribute 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 { ChevronDownIcon } from 'lucide-react'
import { Portal, Select } from '@ark-ui/react'
export const Basic = () => {
const items = ['React', 'Solid', 'Vue']
return (
<Select.Root items={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>
{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 { Index, Portal } from 'solid-js/web'
import { Select } from '@ark-ui/solid'
export const Basic = () => {
const items = ['React', 'Solid', 'Vue']
return (
<Select.Root items={items}>
<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={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 { ref } from 'vue'
import { Select } from '@ark-ui/vue'
import { ChevronDownIcon } from 'lucide-vue-next'
const items = ref(['React', 'Solid', 'Vue'])
</script>
<template>
<Select.Root :items="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>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in 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>
Advanced Customization
For advanced customizations and item properties like disabled
:
import { ChevronDownIcon } from 'lucide-react'
import { Portal, Select } from '@ark-ui/react'
export const Advanced = () => {
type Item = { label: string; value: string; disabled?: boolean }
const items: Item[] = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
]
return (
<Select.Root items={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>
{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 { Index, Portal } from 'solid-js/web'
import { Select } from '@ark-ui/solid'
interface Item {
label: string
value: string
disabled?: boolean
}
export const Advanced = () => {
const items: Item[] = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
]
return (
<Select.Root items={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={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 { ref } from 'vue'
import { Select } from '@ark-ui/vue'
import { ChevronDownIcon } from 'lucide-vue-next'
const items = ref([
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
])
</script>
<template>
<Select.Root :items="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>
<Teleport to="body">
<Select.Positioner>
<Select.Content>
<Select.ItemGroup>
<Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
<Select.Item v-for="item in 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>
Multiple Selection
To enable multiple
item selection:
import { ChevronDownIcon } from 'lucide-react'
import { Portal, Select } from '@ark-ui/react'
export const Multiple = () => {
type Item = { label: string; value: string; disabled?: boolean }
const items: Item[] = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
]
return (
<Select.Root items={items} 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>
{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 { Index, Portal } from 'solid-js/web'
import { Select } from '@ark-ui/solid'
export const Multiple = () => {
const items = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
]
return (
<Select.Root items={items} multiple>
<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>
<Index each={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 { ref } from 'vue'
import { Select } from '@ark-ui/vue'
const items = ref([
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
])
</script>
<template>
<Select.Root :items="items" 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 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>
Controlled Component
For scenarios where you want to control the Select component's state:
import { ChevronDownIcon } from 'lucide-react'
import { useState } from 'react'
import { Portal, Select } from '@ark-ui/react'
export const Controlled = () => {
type Item = { label: string; value: string; disabled?: boolean }
const [_, setSelectedItems] = useState<Item[]>([])
const items: Item[] = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
]
return (
<Select.Root items={items} 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>
{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 { createSignal } from 'solid-js'
import { Index, Portal } from 'solid-js/web'
import { Select } from '@ark-ui/solid'
interface Item {
label: string
value: string
disabled?: boolean
}
export const Controlled = () => {
const [, setSelectedItems] = createSignal<Item[]>([])
const items: Item[] = [
{ label: 'React', value: 'react' },
{ label: 'Solid', value: 'solid' },
{ label: 'Vue', value: 'vue' },
{ label: 'Svelte', value: 'svelte', disabled: true },
]
return (
<Select.Root items={items} 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={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 { ref } from 'vue'
import { Select } from '@ark-ui/vue'
import { ChevronDownIcon } from 'lucide-vue-next'
const items = ref([
{ 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 :items="items" 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 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>
Usage with a Form Library
See how to use the Select component with popular form libraries:
Example not found
Example not found
Example not found
TypeScript Caveats in Vue
Under the hood for React and Solid frameworks, we supply a complex prop type with a generic so that the type of the items
prop matches the param type in the function signatures for props such as the isItemDisabled
prop, say. (See the api reference table below) Unfortunately, generic typing is not supported in Vue for components that contain props with slots and/or emits. Therefore, you will not expect updated typing in this way.
If you have a solution or a workaround to this problem, we would love the contribution and request that you open a Github idea discussion to let us know a PoC you have to share!
API Reference
Root
Prop | Default | Type |
---|---|---|
items | T[] | readonly T[] The options of the select | |
asChild | boolean Render as a different element type. | |
closeOnSelect | boolean Whether the select should close after an item is selected | |
defaultOpen | boolean The initial open state of the select when it is first rendered. Use when you do not need to control its open state. | |
defaultValue | string[] The initial value of the select when it is first rendered. Use when you do not need to control the state of the select. | |
disabled | boolean Whether the select is disabled | |
form | string The associate form of the underlying select. | |
highlightedValue | string The key of the highlighted item | |
id | string The 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. | |
invalid | boolean Whether the select is invalid | |
isItemDisabled | (item: T) => boolean Whether the item is disabled | |
itemToString | (item: T) => string The label of the item | |
itemToValue | (item: T) => string The value of the item | |
lazyMount | false | boolean Whether to enable lazy mounting |
loopFocus | boolean Whether to loop the keyboard navigation through the options | |
multiple | boolean Whether to allow multiple selection | |
name | string The `name` attribute of the underlying select. | |
onExitComplete | () => void Function called when the animation ends in the closed state. | |
onFocusOutside | (event: FocusOutsideEvent) => void Function called when the focus is moved outside the component | |
onHighlightChange | (details: HighlightChangeDetails<T>) => void The callback fired when the highlighted item changes. | |
onInteractOutside | (event: InteractOutsideEvent) => void Function called when an interaction happens outside the component | |
onOpenChange | (details: OpenChangeDetails) => void Function called when the popup is opened | |
onPointerDownOutside | (event: PointerDownOutsideEvent) => void Function called when the pointer is pressed down outside the component | |
onValueChange | (details: ValueChangeDetails<T>) => void The callback fired when the selected item changes. | |
open | boolean Whether the select menu is open | |
positioning | PositioningOptions The positioning options of the menu. | |
present | boolean Whether the node is present (controlled by the user) | |
readOnly | boolean Whether the select is read-only | |
scrollToIndexFn | (details: ScrollToIndexDetails) => void Function to scroll to a specific index | |
unmountOnExit | false | boolean Whether to unmount on exit. |
value | string[] The keys of the selected items |
ClearTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Content
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Control
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
HiddenSelect
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Indicator
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
ItemGroupLabel
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
ItemGroup
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
ItemIndicator
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Item
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. | |
item | any |
ItemText
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Label
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Positioner
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
Trigger
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. |
ValueText
Prop | Default | Type |
---|---|---|
asChild | boolean Render as a different element type. | |
placeholder | string Text to display when no value is selected. |