@cloudflare/kumo
import { Autocomplete } from "@cloudflare/kumo";

/** Basic autocomplete with a flat list of strings. */
export function AutocompleteDemo() {
  return (
    <Autocomplete items={fruits}>
      <Autocomplete.InputGroup
        aria-label="Search fruits"
        placeholder="Search fruits…"
      />
      <Autocomplete.Content>
        <Autocomplete.List>
          {(item: string) => (
            <Autocomplete.Item key={item} value={item}>
              {item}
            </Autocomplete.Item>
          )}
        </Autocomplete.List>
      </Autocomplete.Content>
    </Autocomplete>
  );
}

Installation

Barrel

import { Autocomplete } from "@cloudflare/kumo";

Granular

import { Autocomplete } from "@cloudflare/kumo/components/autocomplete";

When to use

Use Autocomplete when the input value can be free-form text and suggestions are optional hints. Use Combobox instead when the selected value must come from the predefined list.

Accessibility

Every autocomplete input needs an accessible name. Prefer the root label prop for visible field labels. For compact or visually labelled layouts, pass aria-label or aria-labelledby to Autocomplete.InputGroup. Placeholder text is only a hint and does not count as the accessible name.

<Autocomplete items={countries} label="Country">
  <Autocomplete.InputGroup placeholder="Search countries…" />
</Autocomplete>

<Autocomplete items={countries}>
  <Autocomplete.InputGroup
    aria-label="Search countries"
    placeholder="Search countries…"
  />
</Autocomplete>

Controlled

Pass value and onValueChange for controlled usage.

import { useState } from "react";
import { Autocomplete } from "@cloudflare/kumo";

/** Controlled autocomplete with value and onValueChange. */
export function AutocompleteControlledDemo() {
  const [value, setValue] = useState("");

  return (
    <div className="flex flex-col gap-3 w-80">
      <Autocomplete
        items={fruits}
        value={value}
        onValueChange={(v) => setValue(v)}
      >
        <Autocomplete.InputGroup
          aria-label="Type a fruit"
          placeholder="Type a fruit…"
        />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: string) => (
              <Autocomplete.Item key={item} value={item}>
                {item}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
      {value && (
        <p className="text-sm text-kumo-subtle">
          Value: <span className="text-kumo-default font-medium">{value}</span>
        </p>
      )}
    </div>
  );
}

With Field

Add label, description, and required to enable the built-in Field wrapper.

Start typing to filter languages

import { useCallback } from "react";
import { Autocomplete } from "@cloudflare/kumo";
import { languages, Language } from "./data/languages";

/** Autocomplete with label, description, and Field wrapper. */
export function AutocompleteWithFieldDemo() {
  const { contains } = Autocomplete.useFilter();

  const filter = useCallback(
    (item: Language, query: string) => contains(item.label, query),
    [contains],
  );

  return (
    <div className="w-80">
      <Autocomplete
        items={languages}
        label="Language"
        description="Start typing to filter languages"
        filter={filter}
      >
        <Autocomplete.InputGroup placeholder="Search a language…" />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: Language) => (
              <Autocomplete.Item key={item.value} value={item}>
                {item.emoji} {item.label}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
    </div>
  );
}

Error State

Display validation errors with the error prop.

Please enter a valid country
import { useCallback } from "react";
import { Autocomplete } from "@cloudflare/kumo";

/** Autocomplete with error state via the Field wrapper. */
export function AutocompleteErrorDemo() {
  const { contains } = Autocomplete.useFilter();

  const filter = useCallback(
    (item: Country, query: string) => contains(item.label, query),
    [contains],
  );

  return (
    <div className="w-80">
      <Autocomplete
        items={countries}
        label="Country"
        error={{ message: "Please enter a valid country", match: true }}
        filter={filter}
      >
        <Autocomplete.InputGroup placeholder="Search countries…" />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: Country) => (
              <Autocomplete.Item key={item.code} value={item}>
                {item.label}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
    </div>
  );
}

Grouped

Group items into categories using Autocomplete.Group and Autocomplete.GroupLabel.

import { Autocomplete } from "@cloudflare/kumo";

/** Autocomplete with grouped items using Group and GroupLabel. */
export function AutocompleteGroupedDemo() {
  return (
    <Autocomplete items={servers}>
      <Autocomplete.InputGroup
        aria-label="Select server region"
        placeholder="Select region…"
      />
      <Autocomplete.Content>
        <Autocomplete.List>
          {(group: ServerGroup) => (
            <Autocomplete.Group key={group.value} items={group.items}>
              <Autocomplete.GroupLabel>{group.value}</Autocomplete.GroupLabel>
              <Autocomplete.Collection>
                {(item: ServerLocation) => (
                  <Autocomplete.Item key={item.value} value={item}>
                    {item.label}
                  </Autocomplete.Item>
                )}
              </Autocomplete.Collection>
            </Autocomplete.Group>
          )}
        </Autocomplete.List>
      </Autocomplete.Content>
    </Autocomplete>
  );
}

Sizes

The size prop on Autocomplete.InputGroup supports four variants matching the Input component: xs, sm, base (default), and lg.

import { Autocomplete } from "@cloudflare/kumo";

/** Demonstrates the four size variants: xs, sm, base, and lg. */
export function AutocompleteSizesDemo() {
  return (
    <div className="flex flex-wrap items-center gap-4">
      <Autocomplete items={fruits.slice(0, 10)}>
        <Autocomplete.InputGroup
          aria-label="Search fruits, extra small"
          size="xs"
          placeholder="xs"
        />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: string) => (
              <Autocomplete.Item key={item} value={item}>
                {item}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
      <Autocomplete items={fruits.slice(0, 10)}>
        <Autocomplete.InputGroup
          aria-label="Search fruits, small"
          size="sm"
          placeholder="sm"
        />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: string) => (
              <Autocomplete.Item key={item} value={item}>
                {item}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
      <Autocomplete items={fruits.slice(0, 10)}>
        <Autocomplete.InputGroup
          aria-label="Search fruits, default"
          size="base"
          placeholder="base (default)"
        />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: string) => (
              <Autocomplete.Item key={item} value={item}>
                {item}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
      <Autocomplete items={fruits.slice(0, 10)}>
        <Autocomplete.InputGroup
          aria-label="Search fruits, large"
          size="lg"
          placeholder="lg"
        />
        <Autocomplete.Content>
          <Autocomplete.List>
            {(item: string) => (
              <Autocomplete.Item key={item} value={item}>
                {item}
              </Autocomplete.Item>
            )}
          </Autocomplete.List>
        </Autocomplete.Content>
      </Autocomplete>
    </div>
  );
}

Filtering

Filtering is case- and accent-insensitive by default, powered by Intl.Collator under the hood. For string items, no custom filter is needed.

When filtering on a property of object items, use Autocomplete.useFilter() to preserve the built-in accent-insensitive matching:

function LanguagePicker() {
  const { contains } = Autocomplete.useFilter();

  const filter = useCallback(
    (item: Language, query: string) => contains(item.label, query),
    [contains],
  );

  return (
    <Autocomplete items={languages} label="Language" filter={filter}>
      {/* ... */}
    </Autocomplete>
  );
}

To disable filtering entirely (e.g. when results come from a server), pass filter={null}:

<Autocomplete items={results} label="Search results" filter={null}>
  ...
</Autocomplete>

API Reference

Autocomplete

Root component. Wraps all sub-components and manages state.

PropTypeDefaultDescription
items*unknown[]-Array of items to display in the dropdown
valuestring | number | string[]-The controlled input value
openboolean-Whether the popup is open (controlled)
childrenReactNode-Autocomplete content (input group, popup content)
classNamestring-Additional CSS classes
labelReactNode-Label content (enables Field wrapper)
requiredboolean-Whether the field is required
labelTooltipReactNode-Tooltip content to display next to the label
descriptionReactNode-Helper text displayed below the field
errorstring | object-Error message or validation error object

Autocomplete.InputGroup

Styled text input. Provide its accessible name with the root label, or pass aria-label / aria-labelledby here when there is no visible label.

PropTypeDefault
classNamestring-
sizeKumoAutocompleteSize-
placeholderstring-

Autocomplete.Content

Dropdown popup container. Wraps Portal, Positioner, and Popup.

PropTypeDefault
childrenReactNode-
classNamestring-
alignAutocompleteBase.Positioner.Props["align"]-
alignOffsetAutocompleteBase.Positioner.Props["alignOffset"]-
sideAutocompleteBase.Positioner.Props["side"]-
sideOffsetAutocompleteBase.Positioner.Props["sideOffset"]-

Autocomplete.Item

Individual suggestion item in the list.

PropTypeDefault

No component-specific props. Accepts standard HTML attributes.

Additional Sub-components

  • Autocomplete.List — scrollable list container with render prop
  • Autocomplete.Group — groups items under a heading
  • Autocomplete.GroupLabel — heading label for a group
  • Autocomplete.Collection — item container within a group
  • Autocomplete.Separator — horizontal divider between items