Components
Autocomplete ALPHA

Autocomplete

Text field with inline autocomplete to show suggestions while allowing freeform user input.

Documentation

Usage

import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      items={items}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) => new RegExp(inputValue, "i").test(color))
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem item={item} key={item}>
            {item}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Anatomy

import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react";
 
export default () => (
  <Autocomplete>
    <AutocompleteTrigger />
 
    <AutocompleteContent>
      <AutocompleteRadioItem />
      <AutocompleteEmpty />
    </AutocompleteContent>
  </Autocomplete>
);

Data

Use the items prop to pass the collection of data to Autocomplete. Items can be an array of primitive types such as strings or it can be an array of objects.

In case item is an object you need to pass some additional props for Autocomplete to work.

import { Box } from "@optiaxiom/react";
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      isItemDisabled={(item) => Boolean(item.isDisabled)}
      items={items}
      itemToString={(item) => (item ? item.label : "")}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) =>
                new RegExp(inputValue, "i").test(color.label),
              )
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem
            icon={
              <Box
                rounded="sm"
                style={{ aspectRatio: 1, backgroundColor: item.color }}
              />
            }
            item={item}
            key={item.value}
          >
            {item.label}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Filtering

Use the onInputValueChange handler to handle filtering your list of items as the user is typing.

import { Box } from "@optiaxiom/react";
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      isItemDisabled={(item) => Boolean(item.isDisabled)}
      items={items}
      itemToString={(item) => (item ? item.label : "")}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) =>
                new RegExp(inputValue, "i").test(color.label),
              )
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem
            icon={
              <Box
                rounded="sm"
                style={{ aspectRatio: 1, backgroundColor: item.color }}
              />
            }
            item={item}
            key={item.value}
          >
            {item.label}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Controlled

Use the value and defaultValue props on Autocomplete to toggle between controlled and uncontrolled usage. And combine it with onValueChange to listen for changes to the value.

import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { type Color, colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
  const [value, setValue] = useState<Color | null>(colors[1]);
 
  return (
    <Autocomplete
      items={items}
      itemToString={(item) => item?.label || ""}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) =>
                new RegExp(inputValue, "i").test(color.label),
              )
            : colors,
        );
      }}
      onValueChange={setValue}
      value={value}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem item={item} key={item.label}>
            {item.label}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Disabled

Use the optional isItemDisabled prop to configure which items should be disabled. The callback is passed the item and the index number as arguments and expects a boolean result.

import { Box } from "@optiaxiom/react";
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      isItemDisabled={(item) => Boolean(item.isDisabled)}
      items={items}
      itemToString={(item) => (item ? item.label : "")}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) =>
                new RegExp(inputValue, "i").test(color.label),
              )
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem
            icon={
              <Box
                rounded="sm"
                style={{ aspectRatio: 1, backgroundColor: item.color }}
              />
            }
            item={item}
            key={item.value}
          >
            {item.label}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Creatable

We can combine filtering and controlled usage to build creatable autocompletes by allowing the user to add new entries on the fly.

import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { type Color, colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
  const [value, setValue] = useState<Color | null>(null);
 
  const [inputValue, setInputValue] = useState("");
  const filteredItems = inputValue
    ? [
        ...items.filter((color) =>
          new RegExp(inputValue, "i").test(color.label),
        ),
        ...(items.find(
          (color) => color.label.toLowerCase() === inputValue.toLowerCase(),
        )
          ? []
          : [{ label: inputValue, new: true }]),
      ]
    : items;
 
  return (
    <Autocomplete
      items={filteredItems}
      itemToString={(item) => item?.label || ""}
      onInputValueChange={setInputValue}
      onValueChange={(value) => {
        if (value?.new) {
          const newItem = { label: value.label };
          setItems((items) => [...items, newItem]);
          setValue(newItem);
        } else {
          setValue(value);
        }
      }}
      value={value}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {filteredItems.map((item) => (
          <AutocompleteRadioItem item={item} key={item.label}>
            {item.new ? `Create "${item.label}"` : item.label}
          </AutocompleteRadioItem>
        ))}
 
        {filteredItems.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Trigger

By default we use the Input component for the autocomplete trigger which accepts all of the existing input props.

Item

The AutocompleteRadioItem component accepts icon, addonBefore, addonAfter, and description props for additional content inside the items.

import { Box } from "@optiaxiom/react";
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      isItemDisabled={(item) => Boolean(item.isDisabled)}
      items={items}
      itemToString={(item) => (item ? item.label : "")}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) =>
                new RegExp(inputValue, "i").test(color.label),
              )
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem
            icon={
              <Box
                rounded="sm"
                style={{ aspectRatio: 1, backgroundColor: item.color }}
              />
            }
            item={item}
            key={item.value}
          >
            {item.label}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Empty

Use the AutocompleteEmpty component when there are no available items or suggestions.

import { Box } from "@optiaxiom/react";
import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      isItemDisabled={(item) => Boolean(item.isDisabled)}
      items={items}
      itemToString={(item) => (item ? item.label : "")}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) =>
                new RegExp(inputValue, "i").test(color.label),
              )
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        {items.map((item) => (
          <AutocompleteRadioItem
            icon={
              <Box
                rounded="sm"
                style={{ aspectRatio: 1, backgroundColor: item.color }}
              />
            }
            item={item}
            key={item.value}
          >
            {item.label}
          </AutocompleteRadioItem>
        ))}
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Loading

Use the loading prop on AutocompleteContent to show a loading spinner if the initial data for the autocomplete has not loaded yet.

import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
} from "@optiaxiom/react/unstable";
import { useEffect, useState } from "react";
 
import { colors } from "./data";
 
export function App() {
  const [items, setItems] = useState<string[]>([]);
  const [inputValue, setInputValue] = useState("");
  const filteredItems = inputValue
    ? items.filter((color) => new RegExp(inputValue, "i").test(color))
    : items;
 
  const [open, setOpen] = useState(false);
  useEffect(() => {
    const timer = setTimeout(() => setItems(open ? colors : []), 3000);
    return () => clearTimeout(timer);
  }, [open]);
 
  return (
    <Autocomplete
      items={filteredItems}
      onInputValueChange={setInputValue}
      onOpenChange={setOpen}
      open={open}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent loading={items.length === 0}>
        {filteredItems.map((item) => (
          <AutocompleteRadioItem item={item} key={item}>
            {item}
          </AutocompleteRadioItem>
        ))}
 
        {filteredItems.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Virtualized

import {
  Autocomplete,
  AutocompleteContent,
  AutocompleteEmpty,
  AutocompleteRadioItem,
  AutocompleteTrigger,
  AutocompleteVirtualized,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
 
const colors = Array.from({ length: 2000 }).map(
  (_, index) => `Color ${index + 1}`,
);
 
export function App() {
  const [items, setItems] = useState(colors);
 
  return (
    <Autocomplete
      items={items}
      onInputValueChange={(inputValue) => {
        setItems(
          inputValue
            ? colors.filter((color) => new RegExp(inputValue, "i").test(color))
            : colors,
        );
      }}
    >
      <AutocompleteTrigger placeholder="Search a color..." w="224" />
 
      <AutocompleteContent>
        <AutocompleteVirtualized items={items}>
          {(item) => (
            <AutocompleteRadioItem item={item} key={item}>
              {item}
            </AutocompleteRadioItem>
          )}
        </AutocompleteVirtualized>
 
        {items.length === 0 && (
          <AutocompleteEmpty>No result found</AutocompleteEmpty>
        )}
      </AutocompleteContent>
    </Autocomplete>
  );
}

Related

Combobox

Multi-purpose combobox widget to allow selection from a dynamic set of options.

DropdownMenu

Display a dropdown menu.

Select

Single select combobox widget to allow selection from a fixed set of options.

Props

Autocomplete

Doesn't render its own HTML element.

Prop

defaultOpen

The initial open state in uncontrolled mode.

false | true

Default: false

defaultValue

The initial selected value in uncontrolled mode.

Item

disabled

Whether the autocomplete input is disabled.

false | true

isItemDisabled

Return true if items need to be marked as disabled and skipped from keyboard navigation.

(item: Item, index: number) => boolean

items*

The items we want to render.

Item[]

itemToKey

Return a unique key for each item when the object reference will change during renders.

(item: Item) => any

itemToString

Return a string representation of items if they are objects. Needed to show selected values inside triggers.

(item: Item) => string

Default: (value) => (value ? String(value) : "")

onInputValueChange

Handler that is called when input value changes.

(inputValue: string) => void

onOpenChange

Handler that is called when the open state changes.

(open: boolean) => void

onValueChange

Handler that is called when the selected value changes.

(value: Item) => void

open

The open state in controlled mode.

false | true

value

The selected value in controlled mode.

Item

AutocompleteTrigger

Supports all SearchInput props in addition to its own. Renders a <div> element.

Prop

addonAfter

Display content inside the input at the end.

ReactNode

addonBefore

Display content inside the input at the start.

ReactNode

addonPointerEvents

When this prop is set to none clicking empty space inside the addon will focus the input box.

"none" | "auto"

appearance

Control the appearance of the input.

"number" | "default"

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

disabled

Whether the input is disabled.

false | true

error

Whether to show the input error state.

false | true

htmlSize

Control the native input size attribute.

number

size

Control the size of the input.

"md" | "lg" | "xl"

AutocompleteContent

Supports all Box props in addition to its own. Renders a <div> element.

Prop

align

"center" | "end" | "start"

Default: center

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

loading

Whether to show loading spinner inside the menu.

false | true

maxH

Whether to restrict the max-height of the content.

Content is also restricted by the available height in the screen relative to the trigger.

"xs" | "sm" | "md" | "lg" | "full"

minW

Whether to set the min-width to the width of the trigger.

"0" | "trigger"

side

"bottom" | "left" | "right" | "top"

Default: bottom

AutocompleteRadioItem

Supports all Box props in addition to its own. Renders a <div> element.

Prop

addonAfter

Display content inside the item after children.

ReactNode

addonBefore

Display content inside the item before children.

ReactNode

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

description

Add secondary text after the primary label.

ReactNode

icon

Display an icon before the item content.

ReactNode

intent

Control the appearance by selecting between the different item types.

"danger" | "neutral"

item*

The exact item object from the collection.

unknown

selected

Whether to override the default selected state.

false | true

AutocompleteEmpty

Supports all Box props in addition to its own. Renders a <div> element.

Prop

asChild

Change the default rendered element for the one passed as a child, merging their props and behavior.

Read the Composition guide for more details.

false | true

className

string

Changelog

0.2.0

  • Added component