Skip to main content

An autocomplete combines a select with filtering, allowing users to search and select from a list of options.

Import

import { Autocomplete, useFilter } from "heroui-solid";

Usage

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function Default() {
  const { contains } = useFilter({ sensitivity: "base" })

  const [selectedKeys, setSelectedKeys] = createSignal<string[]>([])

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  const onRemoveTags = (keys: Set<string>) => {
    setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select states"
      selectionMode="multiple"
      value={selectedKeys()}
      onChange={(keys) => setSelectedKeys(keys as string[])}
    >
      <Label>States to Visit</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            return (
              <TagGroup size="sm" onRemove={onRemoveTags}>
                <TagGroup.List>
                  <For each={state.selectedItems.map((item) => item.key)}>
                    {(selectedItemKey) => {
                      const item = items.find((s) => s.id === selectedItemKey)

                      if (!item) return null

                      return <Tag id={item.id}>{item.name}</Tag>
                    }}
                  </For>
                </TagGroup.List>
              </TagGroup>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={items}>
              {(item) => (
                <ListBox.Item id={item.id} textValue={item.name}>
                  {item.name}
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Anatomy

Import the Autocomplete component and access all parts using dot notation.

import { Autocomplete, Label, Description, SearchField, ListBox } from "heroui-solid";

export default () => (
  <Autocomplete>
    <Label />
    <Autocomplete.Trigger>
      <Autocomplete.Value />
      <Autocomplete.ClearButton />
      <Autocomplete.Indicator />
    </Autocomplete.Trigger>
    <Description />
    <Autocomplete.Popover>
      <Autocomplete.Filter>
        <SearchField>
          <SearchField.Group>
            <SearchField.SearchIcon />
            <SearchField.Input />
          </SearchField.Group>
        </SearchField>
        <ListBox>
          <ListBox.Item>
            <Label />
            <ListBox.ItemIndicator />
          </ListBox.Item>
        </ListBox>
      </Autocomplete.Filter>
    </Autocomplete.Popover>
  </Autocomplete>
);

With Description

Select your state of residence
import {
  Autocomplete,
  Description,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function WithDescription() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select one"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>State</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search states..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={items}>
              {(item) => (
                <ListBox.Item id={item.id} textValue={item.name}>
                  {item.name}
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
      <Description>Select your state of residence</Description>
    </Autocomplete>
  )
}

Multiple Select

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function MultipleSelect() {
  const [selectedKeys, setSelectedKeys] = createSignal<string[]>([])
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "florida", name: "Florida" },
    { id: "new-york", name: "New York" },
    { id: "illinois", name: "Illinois" },
    { id: "pennsylvania", name: "Pennsylvania" }
  ]

  const onRemoveTags = (keys: Set<string>) => {
    setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select states"
      selectionMode="multiple"
      value={selectedKeys()}
      onChange={(keys) => setSelectedKeys(keys as string[])}
    >
      <Label>States</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            return (
              <TagGroup size="sm" onRemove={onRemoveTags}>
                <TagGroup.List>
                  <For each={state.selectedItems.map((item) => item.key)}>
                    {(selectedItemKey) => {
                      const item = items.find((s) => s.id === selectedItemKey)

                      if (!item) return null

                      return <Tag id={item.id}>{item.name}</Tag>
                    }}
                  </For>
                </TagGroup.List>
              </TagGroup>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={items}>
              {(item) => (
                <ListBox.Item id={item.id} textValue={item.name}>
                  {item.name}
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

With Sections

import {
  Autocomplete,
  EmptyState,
  Header,
  Label,
  ListBox,
  SearchField,
  Separator,
  useFilter
} from "heroui-solid"
import { createSignal } from "solid-js"

export function WithSections() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select a country"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>Country</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search countries..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <ListBox.Section>
              <Header>North America</Header>
              <ListBox.Item id="usa" textValue="United States">
                United States
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="canada" textValue="Canada">
                Canada
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="mexico" textValue="Mexico">
                Mexico
                <ListBox.ItemIndicator />
              </ListBox.Item>
            </ListBox.Section>
            <Separator />
            <ListBox.Section>
              <Header>Europe</Header>
              <ListBox.Item id="uk" textValue="United Kingdom">
                United Kingdom
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="france" textValue="France">
                France
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="germany" textValue="Germany">
                Germany
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="spain" textValue="Spain">
                Spain
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="italy" textValue="Italy">
                Italy
                <ListBox.ItemIndicator />
              </ListBox.Item>
            </ListBox.Section>
            <Separator />
            <ListBox.Section>
              <Header>Asia</Header>
              <ListBox.Item id="japan" textValue="Japan">
                Japan
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="china" textValue="China">
                China
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="india" textValue="India">
                India
                <ListBox.ItemIndicator />
              </ListBox.Item>
              <ListBox.Item id="south-korea" textValue="South Korea">
                South Korea
                <ListBox.ItemIndicator />
              </ListBox.Item>
            </ListBox.Section>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

With Disabled Options

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal } from "solid-js"

export function WithDisabledOptions() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  return (
    <Autocomplete
      class="w-[256px]"
      disabledKeys={["cat", "kangaroo"]}
      placeholder="Select an animal"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>Animal</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search animals..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <ListBox.Item id="dog" textValue="Dog">
              Dog
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="cat" textValue="Cat">
              Cat
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="bird" textValue="Bird">
              Bird
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="kangaroo" textValue="Kangaroo">
              Kangaroo
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="elephant" textValue="Elephant">
              Elephant
              <ListBox.ItemIndicator />
            </ListBox.Item>
            <ListBox.Item id="tiger" textValue="Tiger">
              Tiger
              <ListBox.ItemIndicator />
            </ListBox.Item>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Allows Empty Collection

The allowsEmptyCollection prop enables the autocomplete to function even when there are no items in the collection. This is useful for scenarios where the list might be empty initially or when all items are filtered out.

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"

export function AllowsEmptyCollection() {
  const { contains } = useFilter({ sensitivity: "base" })

  return (
    <Autocomplete
      allowsEmptyCollection
      class="w-[256px]"
      placeholder="Select one"
      selectionMode="single"
    >
      <Label>State</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search states..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          />
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Custom Indicator

import { ChevronsExpandVertical } from "gravity-icons-solid"
import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function CustomIndicator() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select one"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>State</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator class="size-3">
          <ChevronsExpandVertical />
        </Autocomplete.Indicator>
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search states..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={items}>
              {(item) => (
                <ListBox.Item id={item.id} textValue={item.name}>
                  {item.name}
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Required

import {
  Autocomplete,
  Button,
  EmptyState,
  FieldError,
  Form,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { For } from "solid-js"

export function Required() {
  const onSubmit = (e: SubmitEvent) => {
    e.preventDefault()

    alert("Form submitted successfully!")
  }

  const { contains } = useFilter({ sensitivity: "base" })

  const states = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  const countries = [
    { id: "usa", name: "United States" },
    { id: "canada", name: "Canada" },
    { id: "mexico", name: "Mexico" },
    { id: "uk", name: "United Kingdom" },
    { id: "france", name: "France" },
    { id: "germany", name: "Germany" }
  ]

  return (
    <Form class="flex w-[256px] flex-col gap-4" onSubmit={onSubmit}>
      <Autocomplete
        isRequired
        class="w-full"
        name="state"
        placeholder="Select one"
        selectionMode="single"
      >
        <Label>State</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search states..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={states}>
                {(state) => (
                  <ListBox.Item id={state.id} textValue={state.name}>
                    {state.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
        <FieldError />
      </Autocomplete>
      <Autocomplete
        isRequired
        class="w-full"
        name="country"
        placeholder="Select a country"
        selectionMode="single"
      >
        <Label>Country</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search countries..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={countries}>
                {(country) => (
                  <ListBox.Item id={country.id} textValue={country.name}>
                    {country.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
        <FieldError />
      </Autocomplete>
      <Button type="submit">Submit</Button>
    </Form>
  )
}

Full Width

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Surface,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function FullWidth() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  return (
    <Surface class="w-[380px] space-y-4 rounded-3xl p-6">
      <Autocomplete
        fullWidth
        placeholder="Select one"
        selectionMode="single"
        value={selectedKey()}
        variant="secondary"
        onChange={(key) => setSelectedKey(key as string | null)}
      >
        <Label>State</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search states..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={items}>
                {(item) => (
                  <ListBox.Item id={item.id} textValue={item.name}>
                    {item.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
      </Autocomplete>
    </Surface>
  )
}

Variants

The Autocomplete component supports two visual variants:

  • primary (default) - Standard styling with shadow, suitable for most use cases
  • secondary - Lower emphasis variant without shadow, suitable for use in Surface components

Single Select Variants

Multiple Select Variants

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function Variants() {
  const [selectedKey1, setSelectedKey1] = createSignal<string | null>(null)
  const [selectedKey2, setSelectedKey2] = createSignal<string | null>(null)
  const [selectedKeys1, setSelectedKeys1] = createSignal<string[]>([])
  const [selectedKeys2, setSelectedKeys2] = createSignal<string[]>([])
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "option1", name: "Option 1" },
    { id: "option2", name: "Option 2" },
    { id: "option3", name: "Option 3" },
    { id: "option4", name: "Option 4" }
  ]

  const onRemoveTags1 = (keys: Set<string>) => {
    setSelectedKeys1((prev) => prev.filter((key) => !keys.has(key)))
  }

  const onRemoveTags2 = (keys: Set<string>) => {
    setSelectedKeys2((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <div class="flex flex-col gap-8">
      <div class="flex flex-col gap-4">
        <h3 class="text-lg font-semibold">Single Select Variants</h3>
        <div class="flex flex-col gap-4">
          <Autocomplete
            class="w-[256px]"
            placeholder="Select one"
            selectionMode="single"
            value={selectedKey1()}
            variant="primary"
            onChange={(key) => setSelectedKey1(key as string | null)}
          >
            <Label>Primary variant</Label>
            <Autocomplete.Trigger>
              <Autocomplete.Value />
              <Autocomplete.ClearButton />
              <Autocomplete.Indicator />
            </Autocomplete.Trigger>
            <Autocomplete.Popover>
              <Autocomplete.Filter filter={contains}>
                <SearchField autoFocus name="search" variant="secondary">
                  <SearchField.Group>
                    <SearchField.SearchIcon />
                    <SearchField.Input placeholder="Search..." />
                    <SearchField.ClearButton />
                  </SearchField.Group>
                </SearchField>
                <ListBox
                  renderEmptyState={() => (
                    <EmptyState>No results found</EmptyState>
                  )}
                >
                  <For each={items}>
                    {(item) => (
                      <ListBox.Item id={item.id} textValue={item.name}>
                        {item.name}
                        <ListBox.ItemIndicator />
                      </ListBox.Item>
                    )}
                  </For>
                </ListBox>
              </Autocomplete.Filter>
            </Autocomplete.Popover>
          </Autocomplete>
          <Autocomplete
            class="w-[256px]"
            placeholder="Select one"
            selectionMode="single"
            value={selectedKey2()}
            variant="secondary"
            onChange={(key) => setSelectedKey2(key as string | null)}
          >
            <Label>Secondary variant</Label>
            <Autocomplete.Trigger>
              <Autocomplete.Value />
              <Autocomplete.ClearButton />
              <Autocomplete.Indicator />
            </Autocomplete.Trigger>
            <Autocomplete.Popover>
              <Autocomplete.Filter filter={contains}>
                <SearchField autoFocus name="search" variant="secondary">
                  <SearchField.Group>
                    <SearchField.SearchIcon />
                    <SearchField.Input placeholder="Search..." />
                    <SearchField.ClearButton />
                  </SearchField.Group>
                </SearchField>
                <ListBox
                  renderEmptyState={() => (
                    <EmptyState>No results found</EmptyState>
                  )}
                >
                  <For each={items}>
                    {(item) => (
                      <ListBox.Item id={item.id} textValue={item.name}>
                        {item.name}
                        <ListBox.ItemIndicator />
                      </ListBox.Item>
                    )}
                  </For>
                </ListBox>
              </Autocomplete.Filter>
            </Autocomplete.Popover>
          </Autocomplete>
        </div>
      </div>
      <div class="flex flex-col gap-4">
        <h3 class="text-lg font-semibold">Multiple Select Variants</h3>
        <div class="flex flex-col gap-4">
          <Autocomplete
            class="w-[256px]"
            placeholder="Select multiple"
            selectionMode="multiple"
            value={selectedKeys1()}
            variant="primary"
            onChange={(keys) => setSelectedKeys1(keys as string[])}
          >
            <Label>Primary variant</Label>
            <Autocomplete.Trigger>
              <Autocomplete.Value>
                {({ defaultChildren, isPlaceholder, state }) => {
                  if (isPlaceholder || state.selectedItems.length === 0) {
                    return defaultChildren
                  }

                  return (
                    <TagGroup size="sm" onRemove={onRemoveTags1}>
                      <TagGroup.List>
                        <For each={state.selectedItems.map((item) => item.key)}>
                          {(selectedItemKey) => {
                            const item = items.find(
                              (s) => s.id === selectedItemKey
                            )

                            if (!item) return null

                            return <Tag id={item.id}>{item.name}</Tag>
                          }}
                        </For>
                      </TagGroup.List>
                    </TagGroup>
                  )
                }}
              </Autocomplete.Value>
              <Autocomplete.ClearButton />
              <Autocomplete.Indicator />
            </Autocomplete.Trigger>
            <Autocomplete.Popover>
              <Autocomplete.Filter filter={contains}>
                <SearchField autoFocus name="search" variant="secondary">
                  <SearchField.Group>
                    <SearchField.SearchIcon />
                    <SearchField.Input placeholder="Search..." />
                    <SearchField.ClearButton />
                  </SearchField.Group>
                </SearchField>
                <ListBox
                  renderEmptyState={() => (
                    <EmptyState>No results found</EmptyState>
                  )}
                >
                  <For each={items}>
                    {(item) => (
                      <ListBox.Item id={item.id} textValue={item.name}>
                        {item.name}
                        <ListBox.ItemIndicator />
                      </ListBox.Item>
                    )}
                  </For>
                </ListBox>
              </Autocomplete.Filter>
            </Autocomplete.Popover>
          </Autocomplete>
          <Autocomplete
            class="w-[256px]"
            placeholder="Select multiple"
            selectionMode="multiple"
            value={selectedKeys2()}
            variant="secondary"
            onChange={(keys) => setSelectedKeys2(keys as string[])}
          >
            <Label>Secondary variant</Label>
            <Autocomplete.Trigger>
              <Autocomplete.Value>
                {({ defaultChildren, isPlaceholder, state }) => {
                  if (isPlaceholder || state.selectedItems.length === 0) {
                    return defaultChildren
                  }

                  return (
                    <TagGroup
                      size="sm"
                      variant="surface"
                      onRemove={onRemoveTags2}
                    >
                      <TagGroup.List>
                        <For each={state.selectedItems.map((item) => item.key)}>
                          {(selectedItemKey) => {
                            const item = items.find(
                              (s) => s.id === selectedItemKey
                            )

                            if (!item) return null

                            return <Tag id={item.id}>{item.name}</Tag>
                          }}
                        </For>
                      </TagGroup.List>
                    </TagGroup>
                  )
                }}
              </Autocomplete.Value>
              <Autocomplete.ClearButton />
              <Autocomplete.Indicator />
            </Autocomplete.Trigger>
            <Autocomplete.Popover>
              <Autocomplete.Filter filter={contains}>
                <SearchField autoFocus name="search" variant="secondary">
                  <SearchField.Group>
                    <SearchField.SearchIcon />
                    <SearchField.Input placeholder="Search..." />
                    <SearchField.ClearButton />
                  </SearchField.Group>
                </SearchField>
                <ListBox
                  renderEmptyState={() => (
                    <EmptyState>No results found</EmptyState>
                  )}
                >
                  <For each={items}>
                    {(item) => (
                      <ListBox.Item id={item.id} textValue={item.name}>
                        {item.name}
                        <ListBox.ItemIndicator />
                      </ListBox.Item>
                    )}
                  </For>
                </ListBox>
              </Autocomplete.Filter>
            </Autocomplete.Popover>
          </Autocomplete>
        </div>
      </div>
    </div>
  )
}

In Surface

When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Surface,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function FullWidth() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  return (
    <Surface class="w-[380px] space-y-4 rounded-3xl p-6">
      <Autocomplete
        fullWidth
        placeholder="Select one"
        selectionMode="single"
        value={selectedKey()}
        variant="secondary"
        onChange={(key) => setSelectedKey(key as string | null)}
      >
        <Label>State</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search states..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={items}>
                {(item) => (
                  <ListBox.Item id={item.id} textValue={item.name}>
                    {item.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
      </Autocomplete>
    </Surface>
  )
}

Custom Value

You can customize the displayed value using render props:

import {
  Autocomplete,
  Avatar,
  AvatarFallback,
  AvatarImage,
  Description,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function UserSelection() {
  const users = [
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
      email: "bob@heroui.com",
      fallback: "B",
      id: "1",
      name: "Bob"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
      email: "fred@heroui.com",
      fallback: "F",
      id: "2",
      name: "Fred"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
      email: "martha@heroui.com",
      fallback: "M",
      id: "3",
      name: "Martha"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
      email: "john@heroui.com",
      fallback: "J",
      id: "4",
      name: "John"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
      email: "jane@heroui.com",
      fallback: "J",
      id: "5",
      name: "Jane"
    }
  ]

  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select a user"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>User</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            const selectedItems = state.selectedItems

            if (selectedItems.length > 1) {
              return `${selectedItems.length} users selected`
            }

            const selectedItem = users.find(
              (user) => user.id === selectedItems[0]?.key
            )

            if (!selectedItem) {
              return defaultChildren
            }

            return (
              <div class="flex items-center gap-2">
                <Avatar class="size-4" size="sm">
                  <AvatarImage src={selectedItem.avatarUrl} />
                  <AvatarFallback>{selectedItem.fallback}</AvatarFallback>
                </Avatar>
                <span>{selectedItem.name}</span>
              </div>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search users..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={users}>
              {(user) => (
                <ListBox.Item id={user.id} textValue={user.name}>
                  <Avatar size="sm">
                    <AvatarImage src={user.avatarUrl} />
                    <AvatarFallback>{user.fallback}</AvatarFallback>
                  </Avatar>
                  <div class="flex flex-col">
                    <Label>{user.name}</Label>
                    <Description>{user.email}</Description>
                  </div>
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Controlled

Selected: California

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function Controlled() {
  const states = [
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "florida", name: "Florida" },
    { id: "new-york", name: "New York" },
    { id: "illinois", name: "Illinois" },
    { id: "pennsylvania", name: "Pennsylvania" }
  ]

  const [state, setState] = createSignal<string | null>("california")
  const { contains } = useFilter({ sensitivity: "base" })

  const selectedState = () => states.find((s) => s.id === state())

  return (
    <div class="space-y-2">
      <Autocomplete
        class="w-[256px]"
        placeholder="Select a state"
        selectionMode="single"
        value={state()}
        onChange={(key) => setState(key as string | null)}
      >
        <Label>State (controlled)</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search states..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={states}>
                {(state) => (
                  <ListBox.Item id={state.id} textValue={state.name}>
                    {state.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
      </Autocomplete>
      <p class="text-sm text-muted">
        Selected: {selectedState()?.name || "None"}
      </p>
    </div>
  )
}

Controlled Multiple

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function MultipleSelect() {
  const [selectedKeys, setSelectedKeys] = createSignal<string[]>([])
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "florida", name: "Florida" },
    { id: "new-york", name: "New York" },
    { id: "illinois", name: "Illinois" },
    { id: "pennsylvania", name: "Pennsylvania" }
  ]

  const onRemoveTags = (keys: Set<string>) => {
    setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select states"
      selectionMode="multiple"
      value={selectedKeys()}
      onChange={(keys) => setSelectedKeys(keys as string[])}
    >
      <Label>States</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            return (
              <TagGroup size="sm" onRemove={onRemoveTags}>
                <TagGroup.List>
                  <For each={state.selectedItems.map((item) => item.key)}>
                    {(selectedItemKey) => {
                      const item = items.find((s) => s.id === selectedItemKey)

                      if (!item) return null

                      return <Tag id={item.id}>{item.name}</Tag>
                    }}
                  </For>
                </TagGroup.List>
              </TagGroup>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={items}>
              {(item) => (
                <ListBox.Item id={item.id} textValue={item.name}>
                  {item.name}
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Controlled Open State

Autocomplete is closed

import {
  Autocomplete,
  Button,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function ControlledOpenState() {
  const [isOpen, setIsOpen] = createSignal(false)
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  return (
    <div class="space-y-4">
      <Autocomplete
        class="w-[256px]"
        isOpen={isOpen()}
        placeholder="Select one"
        selectionMode="single"
        onOpenChange={setIsOpen}
      >
        <Label>State</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search states..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={items}>
                {(item) => (
                  <ListBox.Item id={item.id} textValue={item.name}>
                    {item.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
      </Autocomplete>
      <Button onClick={() => setIsOpen(!isOpen())}>
        {isOpen() ? "Close" : "Open"} Autocomplete
      </Button>
      <p class="text-sm text-muted">
        Autocomplete is {isOpen() ? "open" : "closed"}
      </p>
    </div>
  )
}

Asynchronous Filtering

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Spinner
} from "heroui-solid"
import { createEffect, createResource, createSignal, onCleanup } from "solid-js"
import { isServer } from "solid-js/web"
import { cn } from "tailwind-variants"

interface Character {
  name: string
}

export function AsynchronousFiltering() {
  const [filterText, setFilterText] = createSignal("")
  const [debouncedText, setDebouncedText] = createSignal("")

  createEffect(() => {
    const text = filterText()
    const timer = setTimeout(() => setDebouncedText(text), 300)

    onCleanup(() => clearTimeout(timer))
  })

  const [characters] = createResource(debouncedText, async (search) => {
    if (isServer) return []

    try {
      const res = await fetch(
        `https://swapi.py4e.com/api/people/?search=${search}`
      )
      const json = await res.json()

      return (json.results ?? []) as Character[]
    } catch {
      return []
    }
  })

  return (
    <Autocomplete
      allowsEmptyCollection
      class="w-[256px]"
      placeholder="Search..."
      selectionMode="single"
    >
      <Label>Search a Star Wars characters</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter
          inputValue={filterText()}
          onInputChange={setFilterText}
        >
          <SearchField
            autoFocus
            class="sticky top-0 z-10"
            name="search"
            variant="secondary"
          >
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search characters..." />
              <Spinner
                size="sm"
                class={cn("absolute top-1/2 right-2 -translate-y-1/2", {
                  "pointer-events-none opacity-0": !characters.loading
                })}
              />
              <SearchField.ClearButton
                class={cn({
                  "pointer-events-none opacity-0": !!characters.loading
                })}
              />
            </SearchField.Group>
          </SearchField>
          <ListBox
            class="max-h-[420px] overflow-y-auto"
            items={characters() ?? []}
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            {(item: Character) => (
              <ListBox.Item id={item.name} textValue={item.name}>
                {item.name}
                <ListBox.ItemIndicator />
              </ListBox.Item>
            )}
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Virtualization

Autocomplete supports virtualization through Virtualizer, enabling efficient rendering of large datasets by displaying only the rows visible within the viewport.

import {
  Autocomplete,
  Description,
  EmptyState,
  Label,
  ListBox,
  ListLayout,
  SearchField,
  useFilter,
  Virtualizer
} from "heroui-solid"
import { createMemo, createSignal } from "solid-js"

interface User {
  email: string
  id: number
  name: string
}

function generateUsers(n: number): User[] {
  const firstNames = [
    "Emma",
    "Liam",
    "Olivia",
    "Noah",
    "Ava",
    "James",
    "Sophia",
    "Oliver",
    "Isabella",
    "Lucas",
    "Mia",
    "Ethan",
    "Charlotte",
    "Mason",
    "Amelia",
    "Logan",
    "Harper",
    "Alexander",
    "Ella",
    "Benjamin"
  ]
  const lastNames = [
    "Smith",
    "Johnson",
    "Williams",
    "Brown",
    "Jones",
    "Garcia",
    "Miller",
    "Davis",
    "Rodriguez",
    "Martinez",
    "Anderson",
    "Taylor",
    "Thomas",
    "Jackson",
    "White",
    "Harris",
    "Clark",
    "Lewis",
    "Robinson",
    "Walker"
  ]
  const users: User[] = []

  for (let i = 0; i < n; i++) {
    const firstName = firstNames[i % firstNames.length]
    const lastName =
      lastNames[Math.floor(i / firstNames.length) % lastNames.length]
    const name = `${firstName} ${lastName}`

    users.push({
      email: `${firstName?.toLowerCase()}.${lastName?.toLowerCase()}@acme.com`,
      id: i + 1,
      name
    })
  }

  return users
}

export function Virtualization() {
  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const [searchQuery, setSearchQuery] = createSignal("")
  const { contains } = useFilter({ sensitivity: "base" })

  const allUsers = generateUsers(1000)

  const filteredUsers = createMemo(() => {
    const query = searchQuery()

    if (!query) return allUsers

    return allUsers.filter(
      (user) => contains(user.name, query) || contains(user.email, query)
    )
  })

  return (
    <Autocomplete
      allowsEmptyCollection
      class="w-[300px]"
      placeholder="Select a user"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>User</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter
          inputValue={searchQuery()}
          onInputChange={setSearchQuery}
        >
          <SearchField
            autoFocus
            class="sticky top-0 z-10"
            name="search"
            variant="secondary"
          >
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search users..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <Virtualizer layout={ListLayout} layoutOptions={{ rowHeight: 50 }}>
            <ListBox
              items={filteredUsers()}
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              {(user: User) => (
                <ListBox.Item id={String(user.id)} textValue={user.name}>
                  <div class="flex flex-col">
                    <Label>{user.name}</Label>
                    <Description>{user.email}</Description>
                  </div>
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </ListBox>
          </Virtualizer>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Disabled

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { For } from "solid-js"

export function Disabled() {
  const { contains } = useFilter({ sensitivity: "base" })

  const items = [
    { id: "florida", name: "Florida" },
    { id: "delaware", name: "Delaware" },
    { id: "california", name: "California" },
    { id: "texas", name: "Texas" },
    { id: "new-york", name: "New York" },
    { id: "washington", name: "Washington" }
  ]

  const countries = [
    { id: "argentina", name: "Argentina" },
    { id: "venezuela", name: "Venezuela" },
    { id: "japan", name: "Japan" },
    { id: "france", name: "France" },
    { id: "italy", name: "Italy" },
    { id: "spain", name: "Spain" }
  ]

  return (
    <div class="flex flex-col gap-4">
      <Autocomplete
        isDisabled
        class="w-[256px]"
        defaultValue="california"
        placeholder="Select one"
        selectionMode="single"
      >
        <Label>State</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search states..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={items}>
                {(item) => (
                  <ListBox.Item id={item.id} textValue={item.name}>
                    {item.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
      </Autocomplete>
      <Autocomplete
        isDisabled
        class="w-[256px]"
        defaultValue={["argentina", "japan", "france"]}
        placeholder="Select countries"
        selectionMode="multiple"
      >
        <Label>Countries to Visit</Label>
        <Autocomplete.Trigger>
          <Autocomplete.Value />
          <Autocomplete.ClearButton />
          <Autocomplete.Indicator />
        </Autocomplete.Trigger>
        <Autocomplete.Popover>
          <Autocomplete.Filter filter={contains}>
            <SearchField autoFocus name="search" variant="secondary">
              <SearchField.Group>
                <SearchField.SearchIcon />
                <SearchField.Input placeholder="Search countries..." />
                <SearchField.ClearButton />
              </SearchField.Group>
            </SearchField>
            <ListBox
              renderEmptyState={() => <EmptyState>No results found</EmptyState>}
            >
              <For each={countries}>
                {(country) => (
                  <ListBox.Item id={country.id} textValue={country.name}>
                    {country.name}
                    <ListBox.ItemIndicator />
                  </ListBox.Item>
                )}
              </For>
            </ListBox>
          </Autocomplete.Filter>
        </Autocomplete.Popover>
      </Autocomplete>
    </div>
  )
}

Advanced Examples

User Selection

import {
  Autocomplete,
  Avatar,
  AvatarFallback,
  AvatarImage,
  Description,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function UserSelection() {
  const users = [
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
      email: "bob@heroui.com",
      fallback: "B",
      id: "1",
      name: "Bob"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
      email: "fred@heroui.com",
      fallback: "F",
      id: "2",
      name: "Fred"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
      email: "martha@heroui.com",
      fallback: "M",
      id: "3",
      name: "Martha"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
      email: "john@heroui.com",
      fallback: "J",
      id: "4",
      name: "John"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
      email: "jane@heroui.com",
      fallback: "J",
      id: "5",
      name: "Jane"
    }
  ]

  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const { contains } = useFilter({ sensitivity: "base" })

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select a user"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>User</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            const selectedItems = state.selectedItems

            if (selectedItems.length > 1) {
              return `${selectedItems.length} users selected`
            }

            const selectedItem = users.find(
              (user) => user.id === selectedItems[0]?.key
            )

            if (!selectedItem) {
              return defaultChildren
            }

            return (
              <div class="flex items-center gap-2">
                <Avatar class="size-4" size="sm">
                  <AvatarImage src={selectedItem.avatarUrl} />
                  <AvatarFallback>{selectedItem.fallback}</AvatarFallback>
                </Avatar>
                <span>{selectedItem.name}</span>
              </div>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search users..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={users}>
              {(user) => (
                <ListBox.Item id={user.id} textValue={user.name}>
                  <Avatar size="sm">
                    <AvatarImage src={user.avatarUrl} />
                    <AvatarFallback>{user.fallback}</AvatarFallback>
                  </Avatar>
                  <div class="flex flex-col">
                    <Label>{user.name}</Label>
                    <Description>{user.email}</Description>
                  </div>
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

User Selection Multiple

import {
  Autocomplete,
  Avatar,
  AvatarFallback,
  AvatarImage,
  Description,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function UserSelectionMultiple() {
  const users = [
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/blue.jpg",
      email: "bob@heroui.com",
      fallback: "B",
      id: "1",
      name: "Bob"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/green.jpg",
      email: "fred@heroui.com",
      fallback: "F",
      id: "2",
      name: "Fred"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/purple.jpg",
      email: "martha@heroui.com",
      fallback: "M",
      id: "3",
      name: "Martha"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/red.jpg",
      email: "john@heroui.com",
      fallback: "J",
      id: "4",
      name: "John"
    },
    {
      avatarUrl:
        "https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/orange.jpg",
      email: "jane@heroui.com",
      fallback: "J",
      id: "5",
      name: "Jane"
    }
  ]

  const [selectedKeys, setSelectedKeys] = createSignal<string[]>([])
  const { contains } = useFilter({ sensitivity: "base" })

  const onRemoveTags = (keys: Set<string>) => {
    setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <Autocomplete
      class="w-[256px]"
      defaultValue={["1", "2"]}
      placeholder="Select your teammates"
      selectionMode="multiple"
      value={selectedKeys()}
      onChange={(keys) => setSelectedKeys(keys as string[])}
    >
      <Label>Users</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            return (
              <TagGroup size="sm" onRemove={onRemoveTags}>
                <TagGroup.List>
                  <For each={state.selectedItems.map((item) => item.key)}>
                    {(selectedItemKey) => {
                      const selectedItem = users.find(
                        (user) => user.id === selectedItemKey
                      )

                      if (!selectedItem) {
                        return null
                      }

                      return (
                        <Tag id={selectedItem.id}>
                          <Avatar class="size-4" size="sm">
                            <AvatarImage src={selectedItem.avatarUrl} />
                            <AvatarFallback>
                              {selectedItem.fallback}
                            </AvatarFallback>
                          </Avatar>
                          <span>{selectedItem.name}</span>
                        </Tag>
                      )
                    }}
                  </For>
                </TagGroup.List>
              </TagGroup>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search users..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No results found</EmptyState>}
          >
            <For each={users}>
              {(user) => (
                <ListBox.Item id={user.id} textValue={user.name}>
                  <Avatar size="sm">
                    <AvatarImage src={user.avatarUrl} />
                    <AvatarFallback>{user.fallback}</AvatarFallback>
                  </Avatar>
                  <div class="flex flex-col">
                    <Label>{user.name}</Label>
                    <Description>{user.email}</Description>
                  </div>
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}
import {
  Autocomplete,
  Description,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

interface City {
  name: string
  country: string
}

export function LocationSearch() {
  const allCities: City[] = [
    { country: "USA", name: "New York" },
    { country: "USA", name: "Los Angeles" },
    { country: "USA", name: "Chicago" },
    { country: "UK", name: "London" },
    { country: "France", name: "Paris" },
    { country: "Japan", name: "Tokyo" },
    { country: "Australia", name: "Sydney" },
    { country: "Canada", name: "Toronto" },
    { country: "Germany", name: "Berlin" },
    { country: "Spain", name: "Madrid" }
  ]

  const [selectedKey, setSelectedKey] = createSignal<string | null>(null)
  const [isLoading, setIsLoading] = createSignal(false)
  const { contains } = useFilter({ sensitivity: "base" })

  // Simulate async filtering
  const customFilter = (text: string, inputValue: string) => {
    if (!inputValue) return true
    setIsLoading(true)
    setTimeout(() => setIsLoading(false), 300)

    return contains(text, inputValue)
  }

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Search for a city"
      selectionMode="single"
      value={selectedKey()}
      onChange={(key) => setSelectedKey(key as string | null)}
    >
      <Label>City</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={customFilter}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search cities..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => (
              <EmptyState>
                {isLoading() ? "Searching..." : "No cities found"}
              </EmptyState>
            )}
          >
            <For each={allCities}>
              {(city) => (
                <ListBox.Item id={city.name} textValue={city.name}>
                  <div class="flex flex-col">
                    <Label>{city.name}</Label>
                    <Description>{city.country}</Description>
                  </div>
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Tag Group Selection

import {
  Autocomplete,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function TagGroupSelection() {
  const tags = [
    { id: "react", name: "React" },
    { id: "typescript", name: "TypeScript" },
    { id: "javascript", name: "JavaScript" },
    { id: "nodejs", name: "Node.js" },
    { id: "python", name: "Python" },
    { id: "vue", name: "Vue" },
    { id: "angular", name: "Angular" },
    { id: "nextjs", name: "Next.js" }
  ]

  const [selectedKeys, setSelectedKeys] = createSignal<string[]>([])
  const { contains } = useFilter({ sensitivity: "base" })

  const onRemoveTags = (keys: Set<string>) => {
    setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Select tags"
      selectionMode="multiple"
      value={selectedKeys()}
      onChange={(keys) => setSelectedKeys(keys as string[])}
    >
      <Label>Tags</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            return (
              <TagGroup size="sm" onRemove={onRemoveTags}>
                <TagGroup.List>
                  <For each={state.selectedItems.map((item) => item.key)}>
                    {(selectedItemKey) => {
                      const tag = tags.find((t) => t.id === selectedItemKey)

                      if (!tag) return null

                      return <Tag id={tag.id}>{tag.name}</Tag>
                    }}
                  </For>
                </TagGroup.List>
              </TagGroup>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search tags..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => <EmptyState>No tags found</EmptyState>}
          >
            <For each={tags}>
              {(tag) => (
                <ListBox.Item id={tag.id} textValue={tag.name}>
                  {tag.name}
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Email Recipients

import {
  Autocomplete,
  Description,
  EmptyState,
  Label,
  ListBox,
  SearchField,
  Tag,
  TagGroup,
  useFilter
} from "heroui-solid"
import { createSignal, For } from "solid-js"

export function EmailRecipients() {
  const emails = [
    {
      email: "alice@example.com",
      id: "alice@example.com",
      name: "Alice Johnson"
    },
    { email: "bob@example.com", id: "bob@example.com", name: "Bob Smith" },
    {
      email: "charlie@example.com",
      id: "charlie@example.com",
      name: "Charlie Brown"
    },
    {
      email: "diana@example.com",
      id: "diana@example.com",
      name: "Diana Prince"
    },
    { email: "eve@example.com", id: "eve@example.com", name: "Eve Wilson" }
  ]

  const [selectedKeys, setSelectedKeys] = createSignal<string[]>([])
  const { contains } = useFilter({ sensitivity: "base" })

  const onRemoveTags = (keys: Set<string>) => {
    setSelectedKeys((prev) => prev.filter((key) => !keys.has(key)))
  }

  return (
    <Autocomplete
      class="w-[256px]"
      placeholder="Add recipients"
      selectionMode="multiple"
      value={selectedKeys()}
      onChange={(keys) => setSelectedKeys(keys as string[])}
    >
      <Label>To</Label>
      <Autocomplete.Trigger>
        <Autocomplete.Value>
          {({ defaultChildren, isPlaceholder, state }) => {
            if (isPlaceholder || state.selectedItems.length === 0) {
              return defaultChildren
            }

            return (
              <TagGroup size="sm" onRemove={onRemoveTags}>
                <TagGroup.List>
                  <For each={state.selectedItems.map((item) => item.key)}>
                    {(selectedItemKey) => {
                      const email = emails.find((e) => e.id === selectedItemKey)

                      if (!email) return null

                      return <Tag id={email.id}>{email.email}</Tag>
                    }}
                  </For>
                </TagGroup.List>
              </TagGroup>
            )
          }}
        </Autocomplete.Value>
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter filter={contains}>
          <SearchField autoFocus name="search" variant="secondary">
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search emails..." />
              <SearchField.ClearButton />
            </SearchField.Group>
          </SearchField>
          <ListBox
            renderEmptyState={() => (
              <EmptyState>No recipients found</EmptyState>
            )}
          >
            <For each={emails}>
              {(email) => (
                <ListBox.Item id={email.id} textValue={email.email}>
                  <div class="flex flex-col">
                    <Label>{email.name}</Label>
                    <Description>{email.email}</Description>
                  </div>
                  <ListBox.ItemIndicator />
                </ListBox.Item>
              )}
            </For>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  )
}

Styling

Passing Tailwind CSS classes

import { Autocomplete, SearchField, ListBox } from "heroui-solid";

function CustomAutocomplete() {
  return (
    <Autocomplete class="w-full">
      <Label>State</Label>
      <Autocomplete.Trigger class="rounded-lg border bg-surface p-2">
        <Autocomplete.Value />
        <Autocomplete.ClearButton />
        <Autocomplete.Indicator />
      </Autocomplete.Trigger>
      <Autocomplete.Popover>
        <Autocomplete.Filter>
          <SearchField>
            <SearchField.Group>
              <SearchField.SearchIcon />
              <SearchField.Input placeholder="Search..." />
            </SearchField.Group>
          </SearchField>
          <ListBox>
            <ListBox.Item id="1" textValue="Item 1" class="hover:bg-surface-secondary">
              Item 1
            </ListBox.Item>
          </ListBox>
        </Autocomplete.Filter>
      </Autocomplete.Popover>
    </Autocomplete>
  );
}

Customizing the component classes

To customize the Autocomplete component classes, you can use the @layer components directive.

@layer components {
  .autocomplete {
    @apply flex flex-col gap-1;
  }

  .autocomplete__trigger {
    @apply rounded-lg border border-border bg-surface p-2;
  }

  .autocomplete__value {
    @apply text-current;
  }

  .autocomplete__clear-button {
    @apply text-muted hover:text-foreground;
  }

  .autocomplete__indicator {
    @apply text-muted;
  }

  .autocomplete__popover {
    @apply rounded-lg border border-border bg-surface p-2;
  }

  .autocomplete__popover-dialog {
    @apply outline-none;
  }
}

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The Autocomplete component uses these CSS classes (View source styles):

Base Classes

  • .autocomplete - Base autocomplete container
  • .autocomplete__trigger - The button that triggers the autocomplete
  • .autocomplete__value - The displayed value or placeholder
  • .autocomplete__clear-button - The clear button that removes the selected value
  • .autocomplete__indicator - The dropdown indicator icon
  • .autocomplete__popover - The popover container
  • .autocomplete__popover-dialog - Internal dialog wrapper inside the popover for focus management (matches Popover behavior)
  • .autocomplete__filter - The filter wrapper

Variant Classes

  • .autocomplete--primary - Primary variant with shadow (default)
  • .autocomplete--secondary - Secondary variant without shadow, suitable for use in surfaces

State Classes

  • .autocomplete[data-invalid="true"] - Invalid state
  • .autocomplete__trigger[data-focus-visible="true"] - Focused trigger state
  • .autocomplete__trigger[data-disabled="true"] - Disabled trigger state
  • .autocomplete__value[data-placeholder="true"] - Placeholder state
  • .autocomplete__clear-button[data-empty="true"] - Clear button hidden when no selection
  • .autocomplete__indicator[data-open="true"] - Open indicator state

Interactive States

The component supports both CSS pseudo-classes and data attributes for flexibility:

  • Hover: :hover or [data-hovered="true"] on trigger
  • Focus: :focus-visible or [data-focus-visible="true"] on trigger
  • Disabled: :disabled or [data-disabled="true"] on autocomplete
  • Open: [data-open="true"] on indicator

API Reference

Autocomplete Props

PropTypeDefaultDescription
placeholderstring'Select an item'Temporary text that occupies the autocomplete when it is empty
selectionMode"single" | "multiple""single"Whether single or multiple selection is enabled
allowsEmptyCollectionbooleanfalseWhether the autocomplete allows an empty collection. When true, the autocomplete can function even with no items.
isOpenboolean-Sets the open state of the popover (controlled)
defaultOpenboolean-Sets the default open state of the popover (uncontrolled)
onOpenChange(isOpen: boolean) => void-Handler called when the open state changes
disabledKeysIterable<string>-Keys of disabled items
isDisabledboolean-Whether the autocomplete is disabled
valuestring | string[] | null-Current value (controlled)
defaultValuestring | string[] | null-Default value (uncontrolled)
onChange(value: string | string[] | null) => void-Handler called when the value changes
isRequiredboolean-Whether user input is required
isInvalidboolean-Whether the autocomplete value is invalid
namestring-The name of the input, used when submitting an HTML form
fullWidthbooleanfalseWhether the autocomplete should take full width of its container
variant"primary" | "secondary""primary"Visual variant of the component. primary is the default style with shadow. secondary is a lower emphasis variant without shadow.
classstring-Additional CSS classes
childrenJSX.Element-Autocomplete content

Autocomplete.Trigger Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-Trigger content

Autocomplete.Value Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element | (state: AutocompleteValueState) => JSX.Element-Value content or render function

Autocomplete.Indicator Props

PropTypeDefaultDescription
classstring-Additional CSS classes
childrenJSX.Element-Custom indicator content

Autocomplete.ClearButton Props

PropTypeDefaultDescription
classstring-Additional CSS classes
onClick(e: MouseEvent) => void-Handler called when button is clicked

Autocomplete.Popover Props

PropTypeDefaultDescription
placementstring"bottom"Placement relative to the trigger (Kobalte placements, e.g. "top")
classstring-Additional CSS classes
childrenJSX.Element-Content children. Wrapped internally in a dialog element for focus management.

Autocomplete.Filter Props

PropTypeDefaultDescription
filter(text: string, input: string) => boolean-Custom filter function
inputValuestring-Controlled input value
onInputChange(value: string) => void-Handler called when input value changes
childrenJSX.Element-Filter content (SearchField and ListBox)

useFilter Hook

The useFilter hook provides filtering functions for autocomplete functionality.

import { useFilter } from "heroui-solid";

const { contains } = useFilter({ sensitivity: "base" });

<Autocomplete.Filter filter={contains}>
  <SearchField>...</SearchField>
  <ListBox>...</ListBox>
</Autocomplete.Filter>;

Options:

OptionTypeDefaultDescription
sensitivity"base" | "accent" | "case" | "variant""base"Locale sensitivity for matching

Returns:

FunctionTypeDescription
contains(string: string, substring: string) => booleanReturns whether a string contains a given substring
startsWith(string: string, substring: string) => booleanReturns whether a string starts with a given substring
endsWith(string: string, substring: string) => booleanReturns whether a string ends with a given substring

RenderProps

When using a render function with Autocomplete.Value, these values are provided:

PropTypeDescription
defaultChildrenJSX.ElementThe default rendered value
isPlaceholderbooleanWhether the value is a placeholder
stateSelectStateThe state of the autocomplete

Accessibility

The Autocomplete component implements the ARIA select pattern with filtering and provides:

  • Full keyboard navigation support
  • Screen reader announcements for selection changes
  • Focus management aligned with Popover: Autocomplete.Popover wraps its content in an internal dialog so touch interactions do not show a stray focus ring on the popover overlay
  • Support for disabled states
  • Search functionality with filtering
  • HTML form integration

Use autoFocus={false} on SearchField when you want to avoid opening the mobile keyboard as soon as the popover appears. Filtering still works once the user focuses the search input.

Differences from HeroUI React

  • Uses class instead of className, and onClick instead of React Aria's onPress.
  • Autocomplete.Value render props receive { defaultChildren, isPlaceholder, state }; state.selectedItems exposes the selected nodes (each with a key).
  • Autocomplete.Popover placements use Kobalte's syntax ("top", "bottom-start", …) instead of React Aria's ("top left").
  • The asynchronous filtering example adapts React Aria's useAsyncList to Solid's createResource with a debounced, controlled inputValue.

Last updated: 7/19/26, 3:27 AM

HeroUI SolidUnofficial SolidJS port of HeroUI v3, built on Kobalte and @heroui/styles