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" | "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 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 ReactNode |
addonBefore Display content inside the item before 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