ComponentsUpdated Sep 9, 2026
Combobox
Type-to-filter selection over a list, single or multiple. Base UI handles filtering and keyboard navigation; the parts here add the Input styling, chips, and the popup chrome.
Import
Source: packages/ui/src/components/combobox
tsx
import { Combobox } from '@highlight/ui'tsx
import { useState } from 'react'
import { Combobox } from '@highlight/ui'
const PEOPLE = ['Lin Chen', 'Vaun Blu', 'Josh Lipson', 'Andrew Lytvynov', 'Sam Quezada', 'Kelsey Chen']
export default function ComboboxSingle() {
const [value, setValue] = useState<string | null>(null)
const [inputValue, setInputValue] = useState('')
return (
<Combobox
items={PEOPLE}
value={value}
onValueChange={(next) => setValue(next as string | null)}
inputValue={inputValue}
onInputValueChange={setInputValue}
>
<Combobox.Input placeholder="Assign to…" />
<Combobox.Content>
<Combobox.Empty>No one matches.</Combobox.Empty>
<Combobox.List>
{(item: string) => (
<Combobox.Item key={item} value={item}>
{item}
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>
)
}tsx
import { useState } from 'react'
import { Combobox } from '@highlight/ui'
const PROJECTS = ['Design foundations', 'Meeting recap v2', 'Onboarding', 'Windows parity']
export default function ComboboxClearable() {
const [value, setValue] = useState<string | null>('Design foundations')
const [inputValue, setInputValue] = useState('')
return (
<Combobox
items={PROJECTS}
value={value}
onValueChange={(next) => setValue(next as string | null)}
inputValue={inputValue}
onInputValueChange={setInputValue}
>
<div className="relative">
<Combobox.Input placeholder="Project…" className="pr-16" />
{value ? (
<div className="absolute right-8 top-1/2 -translate-y-1/2">
<Combobox.Clear />
</div>
) : null}
<div className="absolute right-2 top-1/2 -translate-y-1/2">
<Combobox.Trigger />
</div>
</div>
<Combobox.Content>
<Combobox.Empty>No projects found.</Combobox.Empty>
<Combobox.List>
{(item: string) => (
<Combobox.Item key={item} value={item}>
{item}
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>
)
}Multiple with chips
Anchor the popup to the chips container so it stays aligned as chips wrap.
lin@highlight.ing
tsx
import { useRef, useState } from 'react'
import { Combobox } from '@highlight/ui'
const EMAILS = [
'lin@highlight.ing',
'vaun@highlight.ing',
'josh@highlight.ing',
'sam@highlight.ing',
'kelsey@highlight.ing',
]
export default function ComboboxMultiple() {
const [value, setValue] = useState<Array<string>>(['lin@highlight.ing'])
const anchorRef = useRef<HTMLDivElement>(null)
return (
<Combobox
items={EMAILS}
multiple
value={value}
onValueChange={(next) => setValue(next as Array<string>)}
className="w-96"
>
<Combobox.Chips ref={anchorRef}>
<Combobox.Value>
{(selected: Array<string>) => (
<>
{selected.map((email) => (
<Combobox.Chip key={email}>
{email}
<Combobox.ChipRemove />
</Combobox.Chip>
))}
<Combobox.Input
variant="multiple"
placeholder={selected.length ? '' : 'Invite people…'}
className="min-w-32"
/>
</>
)}
</Combobox.Value>
</Combobox.Chips>
<Combobox.Content anchor={anchorRef}>
<Combobox.Empty>No matches.</Combobox.Empty>
<Combobox.List>
{(item: string) => (
<Combobox.Item key={item} value={item}>
{item}
</Combobox.Item>
)}
</Combobox.List>
</Combobox.Content>
</Combobox>
)
}Parts
Compound sub-components. Every part accepts className and forwards the rest of its props to Base UI.
Combobox.Input- variant: 'single' | 'multiple'. Single matches Input; multiple is borderless inside Chips.
Combobox.Content- Portal + Positioner + Popup. anchor for a custom anchor element.
Combobox.List / Item / Empty- List takes a render function per item.
Combobox.Chips / Chip / ChipRemove / Value- Multi-select display.
Combobox.Trigger / Clear- Open and clear buttons.
Combobox.Group / GroupLabel / Collection / ItemIndicator / Icon- Base UI passthroughs.