Autocomplete
An input that suggests options as you type while keeping free-form text in the field. Suggestions stay optional.
Default
import { Autocomplete } from "@/components/ui/autocomplete"
const tags = [
{ value: "rocket", label: "Rocket" },
{ value: "orbit-bike", label: "Orbit Bike" },
{ value: "lunar-car", label: "Lunar Car" },
{ value: "telemetry", label: "Telemetry" },
]
export default function Example() {
return (
<Autocomplete.Root items={tags}>
<label className="flex w-12 flex-col gap-2 text-sm font-medium">
Search the hangar
<Autocomplete.Input placeholder="E.g. rocket" />
</label>
<Autocomplete.Portal>
<Autocomplete.Positioner>
<Autocomplete.Popup>
<Autocomplete.Empty>Nothing in the hangar matches.</Autocomplete.Empty>
<Autocomplete.List>
{(tag) => (
<Autocomplete.Item key={tag.value} value={tag}>
{tag.label}
</Autocomplete.Item>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
)
}Usage Guidelines
- Need a remembered selection? Use Combobox instead: Autocomplete keeps free-form text, and suggestions are optional.
- Give it an accessible name by wrapping the input in a
<label>, or use Field when you add forms later.
More detail in the Base UI Autocomplete docs.
Anatomy
import { Autocomplete } from "@/components/ui/autocomplete"
<Autocomplete.Root>
<Autocomplete.InputGroup>
<Autocomplete.Input />
<Autocomplete.Trigger />
<Autocomplete.Icon />
<Autocomplete.Clear />
<Autocomplete.Value />
</Autocomplete.InputGroup>
<Autocomplete.Portal>
<Autocomplete.Backdrop />
<Autocomplete.Positioner>
<Autocomplete.Popup>
<Autocomplete.Arrow />
<Autocomplete.Status />
<Autocomplete.Empty />
<Autocomplete.List>
<Autocomplete.Row>
<Autocomplete.Item />
</Autocomplete.Row>
<Autocomplete.Separator />
<Autocomplete.Group>
<Autocomplete.GroupLabel />
</Autocomplete.Group>
<Autocomplete.Collection />
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>With icon and clear button
Put a search icon and clear control inside Autocomplete.InputGroup, which shares one focus ring with the input.
import { SearchIcon } from "lucide-react"
import { Autocomplete } from "@/components/ui/autocomplete"
const tags = [
{ value: "rocket", label: "Rocket" },
{ value: "orbit-bike", label: "Orbit Bike" },
{ value: "lunar-car", label: "Lunar Car" },
]
export default function Example() {
return (
<Autocomplete.Root items={tags}>
<label className="flex w-12 flex-col gap-2 text-sm font-medium">
Search the hangar
<Autocomplete.InputGroup>
<SearchIcon
className="size-4 shrink-0 text-muted-foreground"
aria-hidden
/>
<Autocomplete.Input placeholder="E.g. rocket" />
<Autocomplete.Clear />
</Autocomplete.InputGroup>
</label>
<Autocomplete.Portal>
<Autocomplete.Positioner>
<Autocomplete.Popup>
<Autocomplete.Empty>Nothing in the hangar matches.</Autocomplete.Empty>
<Autocomplete.List>
{(tag) => (
<Autocomplete.Item key={tag.value} value={tag}>
{tag.label}
</Autocomplete.Item>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
)
}Grouped items
Pass groups shaped like { value, items: [] }, and render each with Autocomplete.Group, a label, and Autocomplete.Collection for its items.
import { Autocomplete } from "@/components/ui/autocomplete"
// Groups are objects with an items array. Extra fields like value
// become the group label when you render GroupLabel.
const groupedTags = [
{
value: "Vehicles",
items: [
{ value: "rocket", label: "Rocket" },
{ value: "orbit-bike", label: "Orbit Bike" },
{ value: "lunar-car", label: "Lunar Car" },
],
},
{
value: "Systems",
items: [
{ value: "telemetry", label: "Telemetry" },
{ value: "fuel-tank", label: "Fuel Tank" },
{ value: "ground-control", label: "Ground Control" },
],
},
]
export default function Example() {
return (
<Autocomplete.Root items={groupedTags}>
<label className="flex w-12 flex-col gap-2 text-sm font-medium">
Pick a mission asset
<Autocomplete.Input placeholder="E.g. rocket" />
</label>
<Autocomplete.Portal>
<Autocomplete.Positioner>
<Autocomplete.Popup>
<Autocomplete.Empty>No mission assets found.</Autocomplete.Empty>
<Autocomplete.List>
{(group) => (
<Autocomplete.Group
key={group.value}
items={group.items}
className="pb-1 last:pb-0"
>
<Autocomplete.GroupLabel>{group.value}</Autocomplete.GroupLabel>
<Autocomplete.Collection>
{(tag) => (
<Autocomplete.Item key={tag.value} value={tag}>
{tag.label}
</Autocomplete.Item>
)}
</Autocomplete.Collection>
</Autocomplete.Group>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
)
}Auto highlight
Set autoHighlight so the first match highlights as you type. Use "always" when the list stays visible, such as inside a dialog.
import { Autocomplete } from "@/components/ui/autocomplete"
const tags = [
{ value: "rocket", label: "Rocket" },
{ value: "orbit-bike", label: "Orbit Bike" },
{ value: "lunar-car", label: "Lunar Car" },
]
export default function Example() {
return (
<Autocomplete.Root items={tags} autoHighlight>
<label className="flex w-12 flex-col gap-2 text-sm font-medium">
Auto highlight on type
<Autocomplete.Input placeholder="E.g. rocket" />
</label>
<Autocomplete.Portal>
<Autocomplete.Positioner>
<Autocomplete.Popup>
<Autocomplete.Empty>Nothing in the hangar matches.</Autocomplete.Empty>
<Autocomplete.List>
{(tag) => (
<Autocomplete.Item key={tag.value} value={tag}>
{tag.label}
</Autocomplete.Item>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
)
}Inline autocomplete
With mode="both", the list still filters as you type, and arrowing through options also fills the input with the highlighted label. Set mode to list to filter without filling, inline to fill without filtering, or none for neither.
import { Autocomplete } from "@/components/ui/autocomplete"
const tags = [
{ value: "rocket", label: "Rocket" },
{ value: "orbit-bike", label: "Orbit Bike" },
{ value: "lunar-car", label: "Lunar Car" },
]
export default function Example() {
return (
// mode="both" filters the list and fills the input from the highlighted item.
<Autocomplete.Root items={tags} mode="both">
<label className="flex w-12 flex-col gap-2 text-sm font-medium">
Search the hangar
<Autocomplete.Input placeholder="E.g. rocket" />
</label>
<Autocomplete.Portal>
<Autocomplete.Positioner className="data-empty:hidden">
<Autocomplete.Popup>
<Autocomplete.List>
{(tag) => (
<Autocomplete.Item key={tag.value} value={tag}>
{tag.label}
</Autocomplete.Item>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
)
}Empty state and status
Keep Autocomplete.Empty and Autocomplete.Status mounted, and swap their children rather than the elements themselves so screen readers still announce changes.
import * as React from "react"
import { Autocomplete } from "@/components/ui/autocomplete"
const tags = [
{ value: "rocket", label: "Rocket" },
{ value: "orbit-bike", label: "Orbit Bike" },
{ value: "lunar-car", label: "Lunar Car" },
]
export default function Example() {
const [value, setValue] = React.useState("")
const { contains } = Autocomplete.useFilter({ sensitivity: "base" })
const trimmed = value.trim()
const matchCount = trimmed
? tags.filter((tag) => contains(tag.label, trimmed)).length
: tags.length
return (
<Autocomplete.Root items={tags} value={value} onValueChange={setValue}>
<label className="flex w-12 flex-col gap-2 text-sm font-medium">
Search the hangar
<Autocomplete.Input placeholder="E.g. rocket" />
</label>
<Autocomplete.Portal>
<Autocomplete.Positioner>
<Autocomplete.Popup>
{/* Keep Empty/Status mounted; swap children so screen readers still announce. */}
<Autocomplete.Status>
{matchCount > 0
? `${matchCount} result${matchCount === 1 ? "" : "s"}`
: null}
</Autocomplete.Status>
<Autocomplete.Empty>
{trimmed ? `"${value}" is not cleared for launch.` : null}
</Autocomplete.Empty>
<Autocomplete.List>
{(tag) => (
<Autocomplete.Item key={tag.value} value={tag}>
{tag.label}
</Autocomplete.Item>
)}
</Autocomplete.List>
</Autocomplete.Popup>
</Autocomplete.Positioner>
</Autocomplete.Portal>
</Autocomplete.Root>
)
}Props
Autocomplete.Root
| Prop | Type | Default |
|---|---|---|
| items | ItemValue[] | { items: any[] }[] | — |
| value | string | number | string[] | — |
| defaultValue | string | number | string[] | — |
| onValueChange | (value: string, eventDetails) => void | — |
| open | boolean | — |
| defaultOpen | boolean | false |
| onOpenChange | (open: boolean, eventDetails) => void | — |
| mode | 'list' | 'both' | 'inline' | 'none' | 'list' |
| autoHighlight | boolean | 'always' | false |
| keepHighlight | boolean | false |
| highlightItemOnHover | boolean | true |
| filter | ((itemValue, query, itemToString?) => boolean) | null | — |
| filteredItems | any[] | Group[] | — |
| itemToStringValue | (itemValue: ItemValue) => string | — |
| limit | number | -1 |
| grid | boolean | false |
| inline | boolean | false |
| virtualized | boolean | false |
| openOnInputClick | boolean | false |
| loopFocus | boolean | true |
| modal | boolean | false |
| submitOnItemClick | boolean | false |
| name | string | — |
| disabled | boolean | false |
| readOnly | boolean | false |
| required | boolean | false |
Autocomplete.Input
| Prop | Type | Default |
|---|---|---|
| disabled | boolean | false |
| placeholder | string | — |
Autocomplete.Item
| Prop | Type | Default |
|---|---|---|
| value | any | null |
| disabled | boolean | false |
| onClick | (event) => void | — |
| index | number | — |
Autocomplete.List
| Prop | Type | Default |
|---|---|---|
| children | ReactNode | ((item: any, index: number) => ReactNode) | — |
Autocomplete.Group
| Prop | Type | Default |
|---|---|---|
| items | any[] | — |
Autocomplete.Positioner
| Prop | Type | Default |
|---|---|---|
| side | 'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start' | 'bottom' |
| sideOffset | number | OffsetFunction | 4 |
| align | 'start' | 'center' | 'end' | 'center' |
| alignOffset | number | OffsetFunction | 0 |
| collisionPadding | Padding | 5 |
Autocomplete.useFilter
| Prop | Type | Default |
|---|---|---|
| options | AutocompleteFilterOptions | — |
Autocomplete.useFilteredItems
| Prop | Type | Default |
|---|---|---|
| (no parameters) | returns T[] | — |
This covers the parts used above. See the Base UI Autocomplete docs for Backdrop, Arrow, Row, Value, event detail reasons, and every data attribute and CSS variable.