Input component that allows users to select from a list of options as they type.
import { matchSorter } from 'match-sorter'; const FRUITS = [ { value: 'Apple' }, { value: 'Banana' }, { value: 'Blueberry' }, { value: 'Cherry' }, { value: 'Coconut' }, { value: 'Grape' }, { value: 'Kiwi' }, { value: 'Mango' }, { value: 'Orange' }, { value: 'Papaya' }, { value: 'Peach' }, { value: 'Pineapple' }, { value: 'Strawberry' }, { value: 'Watermelon' }, ]; function ComboboxExample() { const [searchValue, setSearchValue] = React.useState(''); const [selectedValue, setSelectedValue] = React.useState(null); const matches = React.useMemo(() => { return matchSorter(FRUITS, searchValue, { keys: ['value'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); }, [searchValue]); return ( <Combobox.Root selectedValue={selectedValue} onSelectedValueChange={setSelectedValue} onValueChange={(value) => { React.startTransition(() => { setSearchValue(value); }); }} > <Combobox.Anchor> <Combobox.Input placeholder="Select a fruit" /> </Combobox.Anchor> <Combobox.Popover> {matches.length > 0 ? ( <Combobox.Content> <Combobox.ScrollArea> {matches.map((match) => ( <Combobox.Item key={match.value} value={match.value}> {match.value} </Combobox.Item> ))} </Combobox.ScrollArea> </Combobox.Content> ) : ( <Flex align="center" gap="2" justify="between" px="2" py="1"> <Text size="2">No results found</Text> </Flex> )} </Combobox.Popover> </Combobox.Root> ); }
The default combobox is a single-select combobox. You can search for items and select one via the input field.
import { matchSorter } from 'match-sorter'; const FRUITS = [ { value: 'Apple' }, { value: 'Banana' }, { value: 'Blueberry' }, { value: 'Cherry' }, { value: 'Coconut' }, { value: 'Grape' }, { value: 'Kiwi' }, { value: 'Mango' }, { value: 'Orange' }, { value: 'Papaya' }, { value: 'Peach' }, { value: 'Pineapple' }, { value: 'Strawberry' }, { value: 'Watermelon' }, ]; function ComboboxExample() { const [searchValue, setSearchValue] = React.useState(''); const [selectedValue, setSelectedValue] = React.useState(null); const matches = React.useMemo(() => { return matchSorter(FRUITS, searchValue, { keys: ['value'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); }, [searchValue]); return ( <Flex width="280px" direction="column"> <Combobox.Root selectedValue={selectedValue} onSelectedValueChange={setSelectedValue} onValueChange={(value) => { React.startTransition(() => { setSearchValue(value); }); }} > <Combobox.Anchor> <Combobox.Input placeholder="e.g. Apple" /> </Combobox.Anchor> <Combobox.Popover> {matches.length > 0 ? ( <Combobox.Content> <Combobox.ScrollArea> {matches.map((match) => ( <Combobox.Item key={match.value} value={match.value}> {match.value} </Combobox.Item> ))} </Combobox.ScrollArea> </Combobox.Content> ) : ( <Flex align="center" gap="2" justify="between" px="2" py="1"> <Text size="2">No results found</Text> </Flex> )} </Combobox.Popover> </Combobox.Root> </Flex> ); }
The combobox can be used to select multiple items by setting the selectionType prop to multiple. The selected values are displayed in a list below the combobox.
import { matchSorter } from 'match-sorter'; const FRUITS = [ { value: 'Apple' }, { value: 'Banana' }, { value: 'Blueberry' }, { value: 'Cherry' }, { value: 'Coconut' }, { value: 'Grape' }, { value: 'Kiwi' }, { value: 'Mango' }, { value: 'Orange' }, { value: 'Papaya' }, { value: 'Peach' }, { value: 'Pineapple' }, { value: 'Strawberry' }, { value: 'Watermelon' }, ]; function ComboboxExample() { const [searchValue, setSearchValue] = React.useState(''); const [selectedValues, setSelectedValues] = React.useState([]); const [open, setOpen] = React.useState(false); const matches = React.useMemo(() => { return matchSorter(FRUITS, searchValue, { keys: ['value'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); }, [searchValue]); const hasSelectedItems = selectedValues.length > 0; return ( <Flex width="280px" direction="column"> <Combobox.Root selectionType="multiple" selectedValue={selectedValues} onSelectedValueChange={setSelectedValues} open={open} onOpenChange={setOpen} value={searchValue} onValueChange={(value) => { React.startTransition(() => { setSearchValue(value); }); }} > <Flex direction="column" gap="2"> <Flex direction="column" gap="1"> <Combobox.Anchor> <Combobox.Input placeholder={ hasSelectedItems ? `${selectedValues.length} items selected` : 'Select fruits' } /> </Combobox.Anchor> <Combobox.Popover> <Combobox.Content> <Combobox.ScrollArea> {matches.length > 0 ? ( matches.map((match) => ( <Combobox.Item key={match.value} value={match.value}> {match.value} </Combobox.Item> )) ) : ( <Flex align="center" gap="2" justify="between" px="2" py="1" > <Text size="2">No results found</Text> </Flex> )} </Combobox.ScrollArea> </Combobox.Content> </Combobox.Popover> {selectedValues.length > 0 && ( <Combobox.SelectionList> {selectedValues.map((value) => ( <Combobox.SelectionListItem key={value} value={value} onRemove={() => { setSelectedValues( selectedValues.filter((v) => v !== value), ); }} > {value} </Combobox.SelectionListItem> ))} </Combobox.SelectionList> )} </Flex> </Flex> </Combobox.Root> </Flex> ); }
The combobox can be used to create new items by setting the creatable prop to true. The new item is added to the list of items when the user presses enter.
import { matchSorter } from 'match-sorter'; const FRUITS = [ { value: 'Apple' }, { value: 'Banana' }, { value: 'Blueberry' }, { value: 'Cherry' }, { value: 'Coconut' }, { value: 'Grape' }, { value: 'Kiwi' }, { value: 'Mango' }, { value: 'Orange' }, { value: 'Papaya' }, { value: 'Peach' }, { value: 'Pineapple' }, { value: 'Strawberry' }, { value: 'Watermelon' }, ]; function ComboboxExample() { const [{ searchValue, selectedValues, open, fruits }, dispatch] = React.useReducer( (state, action) => { switch (action.type) { case 'set': { const key = action.key; const value = typeof action.value === 'function' ? action.value(state[key]) : action.value; return value === state[key] ? state : { ...state, [key]: value }; } case 'createFruit': { const { fruits } = state; const searchValue = state.searchValue.trim(); const value = searchValue.toLowerCase().charAt(0).toUpperCase() + searchValue.slice(1); const fruit = FRUITS.find((fruit) => fruit.value === value) || { value, }; return { fruits: fruits.some((fruit) => fruit.value === value) ? fruits : [...fruits, fruit], selectedValues: state.selectedValues.includes(value) ? state.selectedValues : [...state.selectedValues, value], open: false, searchValue: '', }; } default: return state; } }, { searchValue: '', selectedValues: [], open: false, fruits: [...FRUITS], }, ); const matches = React.useMemo(() => { return matchSorter(fruits, searchValue, { keys: ['value'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); }, [searchValue]); const hasSelectedItems = selectedValues.length > 0; return ( <Flex width="280px" direction="column"> <Combobox.Root selectionType="multiple" selectedValue={selectedValues} onSelectedValueChange={(value) => dispatch({ type: 'set', key: 'selectedValues', value }) } open={open} onOpenChange={(value) => dispatch({ type: 'set', key: 'open', value })} value={searchValue} onValueChange={(value) => { React.startTransition(() => { dispatch({ type: 'set', key: 'searchValue', value }); }); }} > <Flex direction="column" gap="2"> <Flex direction="column" gap="1"> <Combobox.Anchor> <Combobox.Input placeholder={ hasSelectedItems ? `${selectedValues.length} items selected` : 'Select or create fruits' } /> </Combobox.Anchor> <Combobox.Popover> <Combobox.Content> <Combobox.ScrollArea> {matches.length > 0 ? ( matches.map((match) => ( <Combobox.Item key={match.value} value={match.value}> {match.value} </Combobox.Item> )) ) : ( <Flex align="center" gap="2" justify="between" px="2" py="1" > <Text size="2">No results found</Text> </Flex> )} </Combobox.ScrollArea> {(() => { const cleanSearchValue = searchValue.trim(); if (!cleanSearchValue) { return null; } const matched = matches.find( (match) => match.value.toLowerCase() === cleanSearchValue.toLowerCase(), ); return ( <Combobox.Footer> <Combobox.ActionItem disabled={!!matched} value="create" asChild > <button type="button" onClick={() => !matched && dispatch({ type: 'createFruit' }) } > <Text> Create <Text weight="bold">{searchValue}</Text> </Text> </button> </Combobox.ActionItem> </Combobox.Footer> ); })()} </Combobox.Content> </Combobox.Popover> {selectedValues.length > 0 && ( <Combobox.SelectionList> {selectedValues.map((value) => ( <Combobox.SelectionListItem key={value} value={value} onRemove={() => { dispatch({ type: 'set', key: 'selectedValues', value: selectedValues.filter((v) => v !== value), }); }} > {value} </Combobox.SelectionListItem> ))} </Combobox.SelectionList> )} </Flex> </Flex> </Combobox.Root> </Flex> ); }
function ComboboxExample() { const [searchValue, setSearchValue] = React.useState(''); const [selectedValue, setSelectedValue] = React.useState('apple'); const fruits = [ { value: 'apple', label: 'Apple' }, { value: 'banana', label: 'Banana' }, { value: 'blueberry', label: 'Blueberry' }, { value: 'cherry', label: 'Cherry' }, { value: 'coconut', label: 'Coconut' }, { value: 'grape', label: 'Grape' }, { value: 'kiwi', label: 'Kiwi' }, { value: 'mango', label: 'Mango' }, { value: 'orange', label: 'Orange' }, { value: 'papaya', label: 'Papaya' }, { value: 'peach', label: 'Peach' }, { value: 'pineapple', label: 'Pineapple' }, { value: 'strawberry', label: 'Strawberry' }, { value: 'watermelon', label: 'Watermelon' }, ]; const selectedFruit = fruits.find((f) => f.value === selectedValue); return ( <Combobox.Root selectedValue={selectedValue} onSelectedValueChange={setSelectedValue} onValueChange={(value) => { React.startTransition(() => { setSearchValue(value); }); }} > <Combobox.Anchor> <Combobox.SelectTrigger> {selectedFruit ? selectedFruit.label : 'Select a fruit'} </Combobox.SelectTrigger> </Combobox.Anchor> <Combobox.Popover style={{ minWidth: '280px' }}> <Combobox.Content> <Combobox.Header> <Combobox.Input placeholder="Search fruits..." /> </Combobox.Header> <Combobox.ScrollArea> {fruits.map((fruit) => ( <Combobox.Item key={fruit.value} value={fruit.value}> {fruit.label} </Combobox.Item> ))} </Combobox.ScrollArea> </Combobox.Content> </Combobox.Popover> </Combobox.Root> ); }
import { matchSorter } from 'match-sorter'; const FRUITS = [ { value: '', label: 'No selection' }, { value: 'Apple' }, { value: 'Banana' }, { value: 'Blueberry' }, { value: 'Cherry' }, { value: 'Coconut' }, { value: 'Grape' }, { value: 'Kiwi' }, { value: 'Mango' }, { value: 'Orange' }, { value: 'Papaya' }, { value: 'Peach' }, { value: 'Pineapple' }, { value: 'Strawberry' }, { value: 'Watermelon' }, ]; function ComboboxExample() { const [searchValue, setSearchValue] = React.useState(''); const [selectedValue, setSelectedValue] = React.useState(''); const matches = React.useMemo(() => { return matchSorter(FRUITS, searchValue, { keys: ['value'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); }, [searchValue]); const selectedFruit = FRUITS.find((fruit) => fruit.value === selectedValue); return ( <Combobox.Root selectedValue={selectedValue} onSelectedValueChange={setSelectedValue} onValueChange={(value) => { React.startTransition(() => { setSearchValue(value); }); }} > <Combobox.Anchor> <Combobox.Trigger> <Button ghost> {selectedValue === '' ? 'Select a fruit' : selectedFruit?.label || selectedFruit?.value} </Button> </Combobox.Trigger> </Combobox.Anchor> <Combobox.Popover> <Combobox.Content> <Combobox.Header> <Combobox.Input placeholder="e.g. Apple" /> </Combobox.Header> <Combobox.ScrollArea> {(() => { if (!searchValue || matches.length > 0) { const items = matches.length > 0 ? matches : FRUITS; return items.map((item) => ( <Combobox.Item key={item.value} value={item.value}> {item.label || item.value} </Combobox.Item> )); } return ( <Flex align="center" gap="2" justify="between" px="2" py="1"> <Text size="2">No results found</Text> </Flex> ); })()} </Combobox.ScrollArea> </Combobox.Content> </Combobox.Popover> </Combobox.Root> ); }
Display additional content like actions or information in the header and footer of the popover. You can also use the Combobox.ActionItem component to add actions to the popover.
import { matchSorter } from 'match-sorter'; const FRUITS = [ { value: 'Apple' }, { value: 'Banana' }, { value: 'Blueberry' }, { value: 'Cherry' }, { value: 'Coconut' }, { value: 'Grape' }, { value: 'Kiwi' }, { value: 'Mango' }, { value: 'Orange' }, { value: 'Papaya' }, { value: 'Peach' }, { value: 'Pineapple' }, { value: 'Strawberry' }, { value: 'Watermelon' }, ]; function ComboboxExample() { const [{ searchValue, selectedValue, open, fruits }, dispatch] = React.useReducer( (state, action) => { switch (action.type) { case 'set': { const key = action.key; const value = typeof action.value === 'function' ? action.value(state[key]) : action.value; return value === state[key] ? state : { ...state, [key]: value }; } default: return state; } }, { searchValue: '', selectedValue: null, open: false, fruits: [...FRUITS].slice(0, 3), }, ); const matches = React.useMemo(() => { return matchSorter(fruits, searchValue, { keys: ['value'], baseSort: (a, b) => (a.index < b.index ? -1 : 1), }); }, [searchValue]); return ( <Flex width="280px" direction="column"> <Combobox.Root selectedValue={selectedValue} onSelectedValueChange={(value) => dispatch({ type: 'set', key: 'selectedValue', value }) } open={open} onOpenChange={(value) => dispatch({ type: 'set', key: 'open', value })} value={searchValue} onValueChange={(value) => { React.startTransition(() => { dispatch({ type: 'set', key: 'searchValue', value }); }); }} > <Flex direction="column" gap="2"> <Flex direction="column" gap="1"> <Combobox.Anchor> <Combobox.Input placeholder="Select a fruit" /> </Combobox.Anchor> <Combobox.Popover> <Combobox.Content> <Combobox.Header> <Text size="2">Here are some delicious fruits</Text> </Combobox.Header> <Combobox.ScrollArea> {matches.length > 0 ? ( matches.map((match) => ( <Combobox.Item key={match.value} value={match.value}> {match.value} </Combobox.Item> )) ) : ( <Flex align="center" gap="2" justify="between" px="2" py="1" > <Text size="2">No results found</Text> </Flex> )} </Combobox.ScrollArea> <Combobox.Footer> <Combobox.ActionItem value="about-fruits" asChild> <a href="https://en.wikipedia.org/wiki/Fruit" target="_blank" rel="noopener noreferrer" > Read about fruits </a> </Combobox.ActionItem> <Combobox.ActionItem value="nevermind" asChild> <button type="button" onClick={() => dispatch({ type: 'set', key: 'open', value: false }) } > Nevermind </button> </Combobox.ActionItem> </Combobox.Footer> </Combobox.Content> </Combobox.Popover> </Flex> </Flex> </Combobox.Root> </Flex> ); }