Autocomplete
Autocomplete is a text input that filters a list of options as the user types.
pnpm dlx dreamy add autocompleteBasic usage of Autocomplete. Items are passed as an array of { value, label } objects. The dropdown filters automatically as the user types.
const fruits = [
{ value: "strawberry", label: "Strawberry" },
{ value: "banana", label: "Banana" },
{ value: "orange", label: "Orange" },
{ value: "cherry", label: "Cherry" },
{ value: "mango", label: "Mango" },
];
<Autocomplete.Root items={fruits}>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content />
</Autocomplete.Root>Autocomplete comes with 4 different sizes.
{["sm", "md", "lg"].map((size) => (
<Autocomplete.Root key={size} size={size} items={fruits}>
<Autocomplete.Input placeholder={`Search (${size})...`} />
<Autocomplete.Content />
</Autocomplete.Root>
))}Autocomplete can be used in outline or solid variant.
<Autocomplete.Root variant="outline" items={fruits}>
<Autocomplete.Input placeholder="Outline variant..." />
<Autocomplete.Content />
</Autocomplete.Root>
<Autocomplete.Root variant="filled" items={fruits}>
<Autocomplete.Input placeholder="Filled variant..." />
<Autocomplete.Content />
</Autocomplete.Root>
<Autocomplete.Root variant="flushed" items={fruits}>
<Autocomplete.Input placeholder="Flushed variant..." />
<Autocomplete.Content />
</Autocomplete.Root>
<Autocomplete.Root variant="filledOutline" items={fruits}>
<Autocomplete.Input placeholder="FilledOutline variant..." />
<Autocomplete.Content />
</Autocomplete.Root>Control the selected value externally with value and onChangeValue.
export function ControlledAutocomplete() {
const [value, setValue] = useState<string | null>(null);
return (
<VStack align="start" gap={2}>
<Text>Selected: {value ?? "none"}</Text>
<Autocomplete.Root
width="xs"
items={fruits}
value={value}
onChangeValue={setValue}
>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content />
</Autocomplete.Root>
</VStack>
);
}Pass a filterFn prop to override the default case-insensitive substring matching. The function receives each item and the current query string; return true to include the item.
<Autocomplete.Root
items={fruits}
filterFn={(item, query) =>
item.label.toLowerCase().startsWith(query.toLowerCase())
}
>
<Autocomplete.Input placeholder="Starts-with filter..." />
<Autocomplete.Content />
</Autocomplete.Root>Use the renderItem prop on Autocomplete.Content to fully customise how each item is displayed. The render function receives the filtered AutocompleteItem and must return a React node. Use Autocomplete.Item with the matching value so selection still works correctly.
<Autocomplete.Root items={fruits}>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content
renderItem={(item) => (
<Autocomplete.Item key={item.value} value={item.value}>
<HStack gap={2}>
<LuCherry />
<Text>{item.label}</Text>
</HStack>
</Autocomplete.Item>
)}
/>
</Autocomplete.Root>Pass an icon prop to Autocomplete.Input to show a leading icon inside the input.
<Autocomplete.Root items={fruits}>
<Autocomplete.Input placeholder="Search a fruit..." icon={<LuSearch />} />
<Autocomplete.Content />
</Autocomplete.Root>By default, a clear button appears when a value is selected. Set isClearable={false} to hide it.
<Autocomplete.Root items={fruits} isClearable={false}>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content />
</Autocomplete.Root>Customise the message shown when no items match the query with the noResultsText prop on Autocomplete.Content.
<Autocomplete.Root items={fruits}>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content noResultsText="Nothing here..." />
</Autocomplete.Root>Fetch items when the autocomplete is opened using the onOpen prop. Use noResultsContent on Autocomplete.Content to show a spinner while loading.
export function AsyncAutocomplete() {
const [isLoading, setIsLoading] = useState(true);
const [items, setItems] = useState<AutocompleteItem[]>([]);
function fetchItems() {
if (items.length > 0) return;
fetch("/api/fake-select-data")
.then((res) => res.json())
.then((data: string[]) =>
setItems(data.map((s) => ({ value: s, label: s.charAt(0).toUpperCase() + s.slice(1) })))
)
.finally(() => setIsLoading(false));
}
return (
<Autocomplete.Root items={items} onOpen={fetchItems}>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content
noResultsContent={isLoading ? <Spinner color="primary" py={4} /> : undefined}
/>
</Autocomplete.Root>
);
}For large lists (100+ items), use Autocomplete.VirtualContent instead of Autocomplete.Content. It uses windowed rendering — only visible items are in the DOM — which significantly improves performance and reduces memory usage.
const manyItems = Array.from({ length: 1000 }, (_, i) => ({
value: `item-${i}`,
label: `Item ${i + 1}`,
}));
<Autocomplete.Root items={manyItems}>
<Autocomplete.Input placeholder="Search items..." />
<Autocomplete.VirtualContent />
</Autocomplete.Root>Props
estimatedItemHeight— Estimated height of each item in pixels. For different autocomplete sizes:xs:26,sm:28,md:32,lg:40. Default:32overscan— Number of items to render outside the visible area. Higher values reduce flickering during fast scrolling. Default:5maxHeight— Maximum height of the virtualized list in pixels. Default:300
<Autocomplete.Root items={manyItems}>
<Autocomplete.Input placeholder="Search items..." />
<Autocomplete.VirtualContent
maxHeight={400}
estimatedItemHeight={36}
overscan={10}
/>
</Autocomplete.Root>You can customise the background color of the selected item with the selectedItemBackgroundScheme prop.
primary
success
warning
error
none
{["primary", "success", "warning", "error", "none"].map((scheme) => (
<Autocomplete.Root
key={scheme}
defaultValue="strawberry"
selectedItemBackgroundScheme={scheme}
items={fruits}
>
<Autocomplete.Input placeholder="Search a fruit..." />
<Autocomplete.Content />
</Autocomplete.Root>
))}