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

PropTypeDefault
itemsItemValue[] | { items: any[] }[]
valuestring | number | string[]
defaultValuestring | number | string[]
onValueChange(value: string, eventDetails) => void
openboolean
defaultOpenbooleanfalse
onOpenChange(open: boolean, eventDetails) => void
mode'list' | 'both' | 'inline' | 'none''list'
autoHighlightboolean | 'always'false
keepHighlightbooleanfalse
highlightItemOnHoverbooleantrue
filter((itemValue, query, itemToString?) => boolean) | null
filteredItemsany[] | Group[]
itemToStringValue(itemValue: ItemValue) => string
limitnumber-1
gridbooleanfalse
inlinebooleanfalse
virtualizedbooleanfalse
openOnInputClickbooleanfalse
loopFocusbooleantrue
modalbooleanfalse
submitOnItemClickbooleanfalse
namestring
disabledbooleanfalse
readOnlybooleanfalse
requiredbooleanfalse

Autocomplete.Input

PropTypeDefault
disabledbooleanfalse
placeholderstring

Autocomplete.Item

PropTypeDefault
valueanynull
disabledbooleanfalse
onClick(event) => void
indexnumber

Autocomplete.List

PropTypeDefault
childrenReactNode | ((item: any, index: number) => ReactNode)

Autocomplete.Group

PropTypeDefault
itemsany[]

Autocomplete.Positioner

PropTypeDefault
side'top' | 'bottom' | 'left' | 'right' | 'inline-end' | 'inline-start''bottom'
sideOffsetnumber | OffsetFunction4
align'start' | 'center' | 'end''center'
alignOffsetnumber | OffsetFunction0
collisionPaddingPadding5

Autocomplete.useFilter

PropTypeDefault
optionsAutocompleteFilterOptions

Autocomplete.useFilteredItems

PropTypeDefault
(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.