Combobox
A single input field that combines the functionality of a select and input.
You can explore the combobox component in the following curated examples.
Combobox in Textarea
Render a Combobox that opens when typing in a Textarea.
Combobox with TagsInput
Render a Combobox with tags that can be removed.
Command Menu
Render a Command Palette that opens when CMD + K is pressed.
Menu with Combobox
Create a searchable dropdown menu by combining Menu and Combobox.
Anatomy
To set up the combobox 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 Combobox
component in your project. Let's take a look at the most basic example
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
export const Basic = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: ['React', 'Solid', 'Vue', 'Svelte'],
filter: contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Combobox.Root collection={collection} onInputValueChange={handleInputChange}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
{collection.items.map((item) => (
<Combobox.Item key={item} item={item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
import { Combobox, useListCollection } from '@ark-ui/solid/combobox'
import { useFilter } from '@ark-ui/solid/locale'
import { For } from 'solid-js'
import { Portal } from 'solid-js/web'
const initialItems = ['React', 'Solid', 'Vue', 'Svelte']
export const Basic = () => {
const filterFn = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: filterFn().contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Combobox.Root collection={collection()} onInputValueChange={handleInputChange}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
<For each={collection().items}>
{(item) => (
<Combobox.Item item={item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
)}
</For>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
<script setup lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox, useListCollection } from '@ark-ui/vue/combobox'
import { useFilter } from '@ark-ui/vue/locale'
const filters = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: ['React', 'Solid', 'Vue', 'Svelte'],
filter: filters.value.contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
</script>
<template>
<Combobox.Root :collection="collection" @input-value-change="handleInputChange">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Teleport to="body">
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
<Combobox.Item v-for="item in collection.items" :key="item" :item="item">
<Combobox.ItemText>{{ item }}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Teleport>
</Combobox.Root>
</template>
<script lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox, useListCollection } from '@ark-ui/svelte/combobox'
import { useFilter } from '@ark-ui/svelte/locale'
import { Portal } from '@ark-ui/svelte/portal'
const filters = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: ['React', 'Solid', 'Vue', 'Svelte'],
filter: (...args) => filters().contains(...args),
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
</script>
<Combobox.Root {collection} onInputValueChange={handleInputChange}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
{#each collection().items as item (item)}
<Combobox.Item {item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
{/each}
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
Grouping
To group related combobox items, use the groupBy
prop on the collection and collection.group()
to iterate the
groups.
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
export const Grouping = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: contains,
groupBy: (item) => item.type,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Combobox.Root collection={collection} onInputValueChange={handleInputChange}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
{collection.group().map(([type, group]) => (
<Combobox.ItemGroup key={type}>
<Combobox.ItemGroupLabel>{type}</Combobox.ItemGroupLabel>
{group.map((item) => (
<Combobox.Item key={item.value} item={item}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.ItemGroup>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const initialItems = [
{ label: 'React', value: 'react', type: 'JS' },
{ label: 'Solid', value: 'solid', type: 'JS' },
{ label: 'Vue', value: 'vue', type: 'JS' },
{ label: 'Svelte', value: 'svelte', type: 'JS' },
{ label: 'Panda', value: 'panda', type: 'CSS' },
{ label: 'Tailwind', value: 'tailwind', type: 'CSS' },
]
import { Combobox, useListCollection } from '@ark-ui/solid/combobox'
import { useFilter } from '@ark-ui/solid/locale'
import { For } from 'solid-js'
import { Portal } from 'solid-js/web'
export const Grouping = () => {
const filterFn = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: filterFn().contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Combobox.Root collection={collection()} onInputValueChange={handleInputChange}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<For each={collection().group()}>
{([type, group]) => (
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>{type}</Combobox.ItemGroupLabel>
<For each={group}>
{(item) => (
<Combobox.Item item={item}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
)}
</For>
</Combobox.ItemGroup>
)}
</For>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const initialItems = [
{ 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' },
]
<script setup lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox, useListCollection } from '@ark-ui/vue/combobox'
import { useFilter } from '@ark-ui/vue/locale'
const initialItems = [
{ 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' },
]
const filters = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
groupBy: (item) => item.type,
filter: filters.value.contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
</script>
<template>
<Combobox.Root :collection="collection" @input-value-change="handleInputChange">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Teleport to="body">
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup :key="type" v-for="[type, group] in collection.group()">
<Combobox.ItemGroupLabel>{{ type }}</Combobox.ItemGroupLabel>
<Combobox.Item v-for="item in group" :key="item.value" :item="item">
<Combobox.ItemText>{{ item.label }}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Teleport>
</Combobox.Root>
</template>
<script lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox } from '@ark-ui/svelte/combobox'
import { useListCollection } from '@ark-ui/svelte/collection'
import { useFilter } from '@ark-ui/svelte/locale'
import { Portal } from '@ark-ui/svelte/portal'
const filters = useFilter({ sensitivity: 'base' })
const initialItems = [
{ label: 'React', value: 'react', type: 'JS' },
{ label: 'Solid', value: 'solid', type: 'JS' },
{ label: 'Vue', value: 'vue', type: 'JS' },
{ label: 'Svelte', value: 'svelte', type: 'JS' },
{ label: 'Panda', value: 'panda', type: 'CSS' },
{ label: 'Tailwind', value: 'tailwind', type: 'CSS' },
]
const { collection, filter } = useListCollection({
initialItems,
filter: (...args) => filters().contains(...args),
groupBy: (item) => item.type,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
</script>
<div>
<Combobox.Root {collection} onInputValueChange={handleInputChange}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Select framework..." />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
{#each collection().group() as [type, group]}
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>{type}</Combobox.ItemGroupLabel>
{#each group as item}
<Combobox.Item {item}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
{/each}
</Combobox.ItemGroup>
{/each}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
</div>
Field
The Field
component helps manage form-related state and accessibility attributes of a combobox. It includes handling
ARIA labels, helper text, and error text to ensure proper accessibility.
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { Field } from '@ark-ui/react/field'
import { useFilter } from '@ark-ui/react/locale'
export const WithField = (props: Field.RootProps) => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Field.Root {...props}>
<Combobox.Root collection={collection} onInputValueChange={handleInputChange}>
<Combobox.Label>Label</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Combobox.Positioner>
<Combobox.Content>
{collection.items.map((item) => (
<Combobox.Item key={item} item={item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Combobox.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
}
const initialItems = ['React', 'Solid', 'Vue', 'Svelte']
import { Combobox, useListCollection } from '@ark-ui/solid/combobox'
import { Field } from '@ark-ui/solid/field'
import { useFilter } from '@ark-ui/solid/locale'
import { For } from 'solid-js'
const initialItems = ['React', 'Solid', 'Vue', 'Svelte']
export const WithField = (props: Field.RootProps) => {
const filterFn = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: filterFn().contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Field.Root {...props}>
<Combobox.Root collection={collection()} onInputValueChange={handleInputChange}>
<Combobox.Label>Label</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Combobox.Positioner>
<Combobox.Content>
<For each={collection().items}>
{(item) => (
<Combobox.Item item={item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
)}
</For>
</Combobox.Content>
</Combobox.Positioner>
</Combobox.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
)
}
<script setup lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox, useListCollection } from '@ark-ui/vue/combobox'
import { Field, type FieldRootProps } from '@ark-ui/vue/field'
import { useFilter } from '@ark-ui/vue/locale'
const filters = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: ['React', 'Solid', 'Vue', 'Svelte'],
filter: filters.value.contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
const props = defineProps<FieldRootProps>()
</script>
<template>
<Field.Root v-bind="props">
<Combobox.Root :collection="collection" @input-value-change="handleInputChange">
<Combobox.Label>Label</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
<Combobox.Item v-for="item in collection.items" :key="item" :item="item">
<Combobox.ItemText>{{ item }}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Combobox.Root>
<Field.HelperText>Additional Info</Field.HelperText>
<Field.ErrorText>Error Info</Field.ErrorText>
</Field.Root>
</template>
Example not found
Root Provider
Use the useCombobox
hook to create the combobox store and pass it to the Combobox.RootProvider
component. This
allows you to have maximum control over the combobox programmatically.
import { Combobox, useCombobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
const initialItems = ['React', 'Solid', 'Vue', 'Svelte']
export const RootProvider = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: contains,
})
const combobox = useCombobox({
collection,
onInputValueChange(details) {
filter(details.inputValue)
},
})
return (
<>
<button onClick={() => combobox.focus()}>Focus</button>
<Combobox.RootProvider value={combobox}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
{collection.items.map((item) => (
<Combobox.Item key={item} item={item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
))}
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.RootProvider>
</>
)
}
import { Combobox, useCombobox, useListCollection } from '@ark-ui/solid/combobox'
import { useFilter } from '@ark-ui/solid/locale'
import { For } from 'solid-js'
import { Portal } from 'solid-js/web'
export const RootProvider = () => {
const filterFn = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: ['React', 'Solid', 'Vue', 'Svelte'],
filter: filterFn().contains,
})
const combobox = useCombobox({
get collection() {
return collection()
},
onInputValueChange(details) {
filter(details.inputValue)
},
})
return (
<>
<button onClick={() => combobox().focus()}>Focus</button>
<Combobox.RootProvider value={combobox}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
<For each={collection().items}>
{(item) => (
<Combobox.Item item={item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
)}
</For>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.RootProvider>
</>
)
}
<script setup lang="ts">
import { Combobox, useCombobox, useListCollection } from '@ark-ui/vue/combobox'
import { useFilter } from '@ark-ui/vue/locale'
const filters = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems: ['React', 'Solid', 'Vue', 'Svelte'],
filter: filters.value.contains,
})
const combobox = useCombobox({
get collection() {
return collection.value
},
onInputValueChange(details) {
filter(details.inputValue)
},
})
</script>
<template>
<button @click="combobox.focus()">Focus</button>
<Combobox.RootProvider :value="combobox">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Teleport to="body">
<Combobox.Positioner>
<Combobox.Content>
<Combobox.ItemGroup>
<Combobox.ItemGroupLabel>Frameworks</Combobox.ItemGroupLabel>
<Combobox.Item v-for="item in collection.items" :key="item" :item="item">
<Combobox.ItemText>{{ item }}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
</Combobox.ItemGroup>
</Combobox.Content>
</Combobox.Positioner>
</Teleport>
</Combobox.RootProvider>
</template>
<script lang="ts">
import { Combobox, useCombobox } from '@ark-ui/svelte/combobox'
import { Portal } from '@ark-ui/svelte/portal'
import { createListCollection } from '@ark-ui/svelte/collection'
const collection = createListCollection({
items: ['React', 'Solid', 'Svelte', 'Vue'],
})
const id = $props.id()
const combobox = useCombobox({ collection, id })
</script>
<Combobox.RootProvider value={combobox}>
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input placeholder="Select a Framework" />
<Combobox.Trigger>Open</Combobox.Trigger>
<Combobox.ClearTrigger>Clear</Combobox.ClearTrigger>
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<Combobox.List>
{#each collection.items as item}
<Combobox.Item {item}>
<Combobox.ItemText>{item}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
{/each}
</Combobox.List>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.RootProvider>
If you're using the
Combobox.RootProvider
component, you don't need to use theCombobox.Root
component.
Links
Use the asChild
prop to render the combobox items as links.
import { Combobox, useListCollection } from '@ark-ui/react/combobox'
import { useFilter } from '@ark-ui/react/locale'
import { Portal } from '@ark-ui/react/portal'
export const Links = () => {
const { contains } = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Combobox.Root collection={collection} onInputValueChange={handleInputChange} selectionBehavior="preserve">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
{collection.items.map((item) => (
<Combobox.Item key={item.value} item={item} asChild>
<a href={item.href}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</a>
</Combobox.Item>
))}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const initialItems = [
{ label: 'React', href: 'https://react.dev', value: 'react' },
{ label: 'Solid', href: 'https://solidjs.com', value: 'solid' },
{ label: 'Vue', href: 'https://vuejs.org', value: 'vue' },
{ label: 'Svelte', href: 'https://svelte.dev', value: 'svelte' },
{ label: 'Angular', href: 'https://angular.io', value: 'angular' },
{ label: 'Ember', href: 'https://emberjs.com', value: 'ember' },
{ label: 'Backbone', href: 'https://backbonejs.org', value: 'backbone' },
{ label: 'Polymer', href: 'https://polymer-project.org', value: 'polymer' },
{ label: 'Preact', href: 'https://preactjs.com', value: 'preact' },
{ label: 'Alpine', href: 'https://alpinejs.dev', value: 'alpine' },
{ label: 'Lit', href: 'https://lit.dev', value: 'lit' },
{ label: 'Qwik', href: 'https://qwik.builder.io', value: 'qwik' },
{ label: 'Astro', href: 'https://astro.build', value: 'astro' },
]
import { Combobox, useListCollection } from '@ark-ui/solid/combobox'
import { useFilter } from '@ark-ui/solid/locale'
import { For } from 'solid-js'
import { Portal } from 'solid-js/web'
export const Links = () => {
const filterFn = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: filterFn().contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
return (
<Combobox.Root collection={collection()} onInputValueChange={handleInputChange} selectionBehavior="preserve">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
<For each={collection().items}>
{(item) => (
<Combobox.Item item={item} asChild={(props) => <a href={item.href} {...props} />}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</Combobox.Item>
)}
</For>
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
)
}
const initialItems = [
{ label: 'React', href: 'https://react.dev', value: 'react' },
{ label: 'Solid', href: 'https://solidjs.com', value: 'solid' },
{ label: 'Vue', href: 'https://vuejs.org', value: 'vue' },
{ label: 'Svelte', href: 'https://svelte.dev', value: 'svelte' },
{ label: 'Angular', href: 'https://angular.io', value: 'angular' },
{ label: 'Ember', href: 'https://emberjs.com', value: 'ember' },
{ label: 'Backbone', href: 'https://backbonejs.org', value: 'backbone' },
{ label: 'Polymer', href: 'https://polymer-project.org', value: 'polymer' },
{ label: 'Preact', href: 'https://preactjs.com', value: 'preact' },
{ label: 'Alpine', href: 'https://alpinejs.dev', value: 'alpine' },
{ label: 'Lit', href: 'https://lit.dev', value: 'lit' },
{ label: 'Qwik', href: 'https://qwik.builder.io', value: 'qwik' },
{ label: 'Astro', href: 'https://astro.build', value: 'astro' },
]
<script setup lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox, useListCollection } from '@ark-ui/vue/combobox'
import { useFilter } from '@ark-ui/vue/locale'
const initialItems = [
{ label: 'React', href: 'https://react.dev', value: 'react' },
{ label: 'Solid', href: 'https://solidjs.com', value: 'solid' },
{ label: 'Vue', href: 'https://vuejs.org', value: 'vue' },
{ label: 'Svelte', href: 'https://svelte.dev', value: 'svelte' },
{ label: 'Angular', href: 'https://angular.io', value: 'angular' },
{ label: 'Ember', href: 'https://emberjs.com', value: 'ember' },
{ label: 'Backbone', href: 'https://backbonejs.org', value: 'backbone' },
{ label: 'Polymer', href: 'https://polymer-project.org', value: 'polymer' },
{ label: 'Preact', href: 'https://preactjs.com', value: 'preact' },
{ label: 'Alpine', href: 'https://alpinejs.dev', value: 'alpine' },
{ label: 'Lit', href: 'https://lit.dev', value: 'lit' },
{ label: 'Qwik', href: 'https://qwik.builder.io', value: 'qwik' },
{ label: 'Astro', href: 'https://astro.build', value: 'astro' },
]
const filters = useFilter({ sensitivity: 'base' })
const { collection, filter } = useListCollection({
initialItems,
filter: filters.value.contains,
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
</script>
<template>
<Combobox.Root :collection="collection" @input-value-change="handleInputChange" selection-behavior="preserve">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
</Combobox.Control>
<Teleport to="body">
<Combobox.Positioner>
<Combobox.Content>
<Combobox.Item v-for="item in collection.items" :key="item.value" :item="item" as-child>
<a :href="item.href">
<Combobox.ItemText>{{ item.label }}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</a>
</Combobox.Item>
</Combobox.Content>
</Combobox.Positioner>
</Teleport>
</Combobox.Root>
</template>
<script lang="ts">
// biome-ignore lint/style/useImportType: <explanation>
import { Combobox } from '@ark-ui/svelte/combobox'
import { useListCollection } from '@ark-ui/svelte/collection'
import { useFilter } from '@ark-ui/svelte/locale'
import { Portal } from '@ark-ui/svelte/portal'
const filters = useFilter({ sensitivity: 'base' })
const initialItems = [
{ label: 'React', href: 'https://react.dev', value: 'react' },
{ label: 'Solid', href: 'https://solidjs.com', value: 'solid' },
{ label: 'Vue', href: 'https://vuejs.org', value: 'vue' },
{ label: 'Svelte', href: 'https://svelte.dev', value: 'svelte' },
{ label: 'Angular', href: 'https://angular.io', value: 'angular' },
{ label: 'Ember', href: 'https://emberjs.com', value: 'ember' },
{ label: 'Backbone', href: 'https://backbonejs.org', value: 'backbone' },
{ label: 'Polymer', href: 'https://polymer-project.org', value: 'polymer' },
{ label: 'Preact', href: 'https://preactjs.com', value: 'preact' },
{ label: 'Alpine', href: 'https://alpinejs.dev', value: 'alpine' },
{ label: 'Lit', href: 'https://lit.dev', value: 'lit' },
{ label: 'Qwik', href: 'https://qwik.builder.io', value: 'qwik' },
{ label: 'Astro', href: 'https://astro.build', value: 'astro' },
]
const { collection, filter } = useListCollection({
initialItems,
filter: (...args) => filters().contains(...args),
})
const handleInputChange = (details: Combobox.InputValueChangeDetails) => {
filter(details.inputValue)
}
</script>
<div>
<Combobox.Root {collection} onInputValueChange={handleInputChange} selectionBehavior="preserve">
<Combobox.Label>Framework</Combobox.Label>
<Combobox.Control>
<Combobox.Input />
</Combobox.Control>
<Portal>
<Combobox.Positioner>
<Combobox.Content>
{#each collection().items as item (item.value)}
<Combobox.Item {item}>
{#snippet asChild(props)}
<a {...props()} href={item.href}>
<Combobox.ItemText>{item.label}</Combobox.ItemText>
<Combobox.ItemIndicator>✓</Combobox.ItemIndicator>
</a>
{/snippet}
</Combobox.Item>
{/each}
</Combobox.Content>
</Combobox.Positioner>
</Portal>
</Combobox.Root>
</div>
For custom router links, you can customize the navigate
prop on the Combobox.Root
component.
Here's an example of using the Tanstack Router.
import { Combobox } from '@ark-ui/react/combobox'
import { useNavigate } from '@tanstack/react-router'
function Demo() {
const navigate = useNavigate()
return (
<Combobox.Root
navigate={(e) => {
navigate({ to: e.node.href })
}}
>
{/* ... */}
</Combobox.Root>
)
}
Guides
Available height and width
The following css variables are exposed to the Combobox.Positioner
which you can use to style the Combobox.Content
/* width of the combobox control */
--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='combobox'][data-part='content'] {
max-height: calc(var(--available-height) - 100px);
}
API Reference
Root
Prop | Default | Type |
---|---|---|
collection | ListCollection<T> The collection of items | |
allowCustomValue | boolean Whether to allow typing custom values in the input | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
autoFocus | boolean Whether to autofocus the input on mount | |
closeOnSelect | boolean Whether to close the combobox when an item is selected. | |
composite | true | boolean Whether the combobox is a composed with other composite widgets like tabs |
defaultHighlightedValue | string The initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox. | |
defaultInputValue | '' | string The initial value of the combobox's input when rendered. Use when you don't need to control the value of the combobox's input. |
defaultOpen | boolean The initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox. | |
defaultValue | [] | string[] The initial value of the combobox's selected items when rendered. Use when you don't need to control the value of the combobox's selected items. |
disabled | boolean Whether the combobox is disabled | |
disableLayer | boolean Whether to disable registering this a dismissable layer | |
form | string The associate form of the combobox. | |
highlightedValue | string The controlled highlighted value of the combobox | |
id | string The unique identifier of the machine. | |
ids | Partial<{
root: string
label: string
control: string
input: string
content: string
trigger: string
clearTrigger: string
item(id: string, index?: number | undefined): string
positioner: string
itemGroup(id: string | number): string
itemGroupLabel(id: string | number): string
}> The ids of the elements in the combobox. Useful for composition. | |
immediate | boolean Whether to synchronize the present change immediately or defer it to the next frame | |
inputBehavior | 'none' | 'none' | 'autohighlight' | 'autocomplete' Defines the auto-completion behavior of the combobox. - `autohighlight`: The first focused item is highlighted as the user types - `autocomplete`: Navigating the listbox with the arrow keys selects the item and the input is updated |
inputValue | string The controlled value of the combobox's input | |
invalid | boolean Whether the combobox is invalid | |
lazyMount | false | boolean Whether to enable lazy mounting |
loopFocus | true | boolean Whether to loop the keyboard navigation through the items |
multiple | boolean Whether to allow multiple selection. **Good to know:** When `multiple` is `true`, the `selectionBehavior` is automatically set to `clear`. It is recommended to render the selected items in a separate container. | |
name | string The `name` attribute of the combobox's input. Useful for form submission | |
navigate | (details: NavigateDetails) => void Function to navigate to the selected item | |
onExitComplete | VoidFunction 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 Function called when an item is highlighted using the pointer or keyboard navigation. | |
onInputValueChange | (details: InputValueChangeDetails) => void Function called when the input's value 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 | |
onSelect | (details: SelectionDetails) => void Function called when an item is selected | |
onValueChange | (details: ValueChangeDetails<T>) => void Function called when a new item is selected | |
open | boolean The controlled open state of the combobox | |
openOnChange | true | boolean | ((details: InputValueChangeDetails) => boolean) Whether to show the combobox when the input value changes |
openOnClick | false | boolean Whether to open the combobox popup on initial click on the input |
openOnKeyPress | true | boolean Whether to open the combobox on arrow key press |
placeholder | string The placeholder text of the combobox's input | |
positioning | { placement: 'bottom-start' } | PositioningOptions The positioning options to dynamically position the menu |
present | boolean Whether the node is present (controlled by the user) | |
readOnly | boolean Whether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it | |
required | boolean Whether the combobox is required | |
scrollToIndexFn | (details: ScrollToIndexDetails) => void Function to scroll to a specific index | |
selectionBehavior | 'replace' | 'replace' | 'clear' | 'preserve' The behavior of the combobox input when an item is selected - `replace`: The selected item string is set as the input value - `clear`: The input value is cleared - `preserve`: The input value is preserved |
skipAnimationOnMount | false | boolean Whether to allow the initial presence animation. |
translations | IntlTranslations Specifies the localized strings that identifies the accessibility elements and their states | |
unmountOnExit | false | boolean Whether to unmount on exit. |
value | string[] The controlled value of the combobox's selected items |
Data Attribute | Value |
---|---|
[data-scope] | combobox |
[data-part] | root |
[data-invalid] | Present when invalid |
[data-readonly] | Present when read-only |
ClearTrigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | clear-trigger |
[data-invalid] | Present when invalid |
Content
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | content |
[data-state] | "open" | "closed" |
[data-placement] | The placement of the content |
[data-empty] |
Control
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | control |
[data-state] | "open" | "closed" |
[data-focus] | Present when focused |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
Input
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | input |
[data-invalid] | Present when invalid |
[data-autofocus] | |
[data-state] | "open" | "closed" |
ItemGroupLabel
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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 | boolean Use 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] | combobox |
[data-part] | item-group |
[data-empty] |
ItemIndicator
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | item-indicator |
[data-state] | "checked" | "unchecked" |
Item
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
item | any The item to render | |
persistFocus | boolean Whether hovering outside should clear the highlighted state |
Data Attribute | Value |
---|---|
[data-scope] | combobox |
[data-part] | item |
[data-highlighted] | Present when highlighted |
[data-state] | "checked" | "unchecked" |
[data-disabled] | Present when disabled |
[data-value] | The value of the item |
ItemText
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | item-text |
[data-state] | "checked" | "unchecked" |
[data-disabled] | Present when disabled |
[data-highlighted] | Present when highlighted |
Label
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | label |
[data-readonly] | Present when read-only |
[data-disabled] | Present when disabled |
[data-invalid] | Present when invalid |
[data-focus] | Present when focused |
List
Prop | Default | Type |
---|---|---|
asChild | boolean Use 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] | combobox |
[data-part] | list |
[data-empty] |
Positioner
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
RootProvider
Prop | Default | Type |
---|---|---|
value | UseComboboxReturn<T> | |
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
immediate | boolean Whether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | boolean Whether to enable lazy mounting |
onExitComplete | VoidFunction Function called when the animation ends in the closed state | |
present | boolean Whether the node is present (controlled by the user) | |
skipAnimationOnMount | false | boolean Whether to allow the initial presence animation. |
unmountOnExit | false | boolean Whether to unmount on exit. |
Trigger
Prop | Default | Type |
---|---|---|
asChild | boolean Use the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. | |
focusable | boolean Whether the trigger is focusable |
Data Attribute | Value |
---|---|
[data-scope] | combobox |
[data-part] | trigger |
[data-state] | "open" | "closed" |
[data-invalid] | Present when invalid |
[data-focusable] | |
[data-readonly] | Present when read-only |
[data-disabled] | Present when disabled |
Accessibility
Complies with the Combobox WAI-ARIA design pattern.
Keyboard Support
Key | Description |
---|---|
ArrowDown | When the combobox is closed, opens the listbox and highlights to the first option. When the combobox is open, moves focus to the next option. |
ArrowUp | When the combobox is closed, opens the listbox and highlights to the last option. When the combobox is open, moves focus to the previous option. |
Home | When the combobox is open, moves focus to the first option. |
End | When the combobox is open, moves focus to the last option. |
Escape | Closes the listbox. |
Enter | Selects the highlighted option and closes the combobox. |
Esc | Closes the combobox |