Combobox
Pick one or many options from a searchable list, with groups, chips and async server search.
Preview
Usage
Options come from the z.enum. labels gives each value a readable name.
import { z } from 'zod'
import { AutoForm } from '@vitnode/core/components/form/auto-form'
import { AutoFormCombobox } from '@vitnode/core/components/form/fields/combobox'
const formSchema = z.object({
category: z.enum(['general', 'support']),
})<AutoForm
formSchema={formSchema}
fields={[
{
id: 'category',
component: (props) => (
<AutoFormCombobox
{...props}
label="Category"
labels={[
{ value: 'general', label: 'General discussion' },
{ value: 'support', label: 'Help & support' },
]}
/>
),
},
]}
/>Groups and clear button
Give options a group to sort them under headings. Groups follow the order they
first appear in labels; ungrouped options sit at the top. showClear adds an
× that empties the field.
<AutoFormCombobox
{...props}
labels={[
{ group: 'Europe', label: 'Warsaw', value: 'europe-warsaw' },
{ group: 'Americas', label: 'New York', value: 'america-new-york' },
]}
showClear
/>Async search
fetchData loads options from your API as people type (debounced by 500ms). It
needs a stable id and a queryKey for caching. Each picked value is a
{ value, label } object, not a string.
import { fetcher } from '@vitnode/core/tanstack/fetcher'
const formSchema = z.object({
moderators: z
.array(z.object({ value: z.string(), label: z.string() }))
.min(1)
.default([]),
})
;<AutoFormCombobox
{...props}
fetchData={async ({ search }) => {
const res = await fetcher({
plugin: '@acme/forum',
method: 'get',
module: 'members',
path: '/',
args: { query: { search } },
})
const data = await res.json()
return data.edges.map((member) => ({
label: member.name,
value: member.id.toString(),
}))
}}
id="moderators"
multiple
queryKey={['forum', 'members']}
renderItem={(item) => <UserRow name={item.label} />}
searchPlaceholder="Find a member..."
/>renderItem customizes each async option, like adding an avatar.
Multiple
Add multiple and use an array in the schema. Picks become chips, and the box
wraps when full. .min() and .max() on the array become the validation
messages.
const formSchema = z.object({
topics: z
.array(z.enum(['news', 'guides', 'releases']))
.min(1, 'Pick at least one topic')
.max(3, 'Three topics is plenty'),
})<AutoFormCombobox {...props} labels={topics} maxVisibleChips={3} multiple />maxVisibleChipsshows the first few chips plus "+N more". Every pick is still saved.renderChipcustomizes each chip.- Backspace in an empty search removes the last chip. Escape with the list closed clears every pick, so mind that second Escape.
- Reduced motion turns off the chip and chevron animations.
For a manual multi-select, wrap chips in ComboboxChips and anchor the popup to
it:
const anchor = useComboboxAnchor()
;<Combobox items={frameworks} multiple>
<ComboboxChips ref={anchor}>
<ComboboxValue>
{(values: string[]) => (
<>
{values.map((value) => (
<ComboboxChip key={value}>{value}</ComboboxChip>
))}
<ComboboxChipsInput placeholder="Add a framework" />
</>
)}
</ComboboxValue>
</ComboboxChips>
<ComboboxContent anchor={anchor}>
<ComboboxList>
{(item) => (
<ComboboxItem key={item} value={item}>
{item}
</ComboboxItem>
)}
</ComboboxList>
</ComboboxContent>
</Combobox>Props
Prop
Type