Autocomplete

Autocomplete is a text input that filters a list of options as the user types.

pnpm dlx dreamy add autocomplete

Basic 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: 32
  • overscan — Number of items to render outside the visible area. Higher values reduce flickering during fast scrolling. Default: 5
  • maxHeight — 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>
))}