Skip to contentSkip to navigationSkip to topbar
Paste assistant Assistant
Figma
Star

Combobox

Version 16.2.1GithubStorybook

A Combobox is a styled dropdown form element that allows users to either type a value or select a value from a list.

Component preview theme
const authors = ['James Baldwin', 'Adrienne Maree Brown', 'Octavia Butler', 'Ta-Nehisi Coates', 'Audre Lorde', 'Nnedi Okorafor'];
const BasicCombobox = () => {
return (
<Combobox items={authors} initialSelectedItem={authors[2]} labelText="Select an author" required />
);
};
render(
<BasicCombobox />
)

Guidelines

Guidelines page anchor

About Combobox

About Combobox page anchor

A Combobox allows a user to select a single value from a styled listbox of options. Each option can consist of more than just text, e.g. text paired with an icon. However, an option can only be paired with non-interactive content and shouldn’t contain components like a checkbox or button. It can also be set up with autocomplete functionality so users can easily find a specific option.

This component is an opinionated implementation and wrapper around the Downshift library(link takes you to an external page).

What’s the difference between Select and Combobox?

What’s the difference between Select and Combobox? page anchor

At its most basic, a Select has an options list that’s styled according to the browser default. A Combobox has a Twilio-styled options list and can allow for additional functionality like autocomplete and grouping options.

Use a Select when:

  • You need a native picker experience, especially on mobile devices.
  • You only need to show text in an option.
  • Users will be selecting from a list of 4-10 options, or a sorted list of highly familiar options (e.g., alphabetical list of states or countries). For less than 4 items, consider using a Radio Group instead.
  • You need the component to work out-of-the-box across all operating systems and browsers.

Use a Combobox when:

  • You need a Twilio-styled options list.
  • You need to show more than text in an option (e.g., text paired with an icon).
  • You need to group options under labels.
  • You need to disable options in the list.
  • Users would benefit from autocomplete functionality (e.g., autocomplete, search). For example, autocomplete may be useful when users need to select from a list of more than 10 options.
  • You need to lazy load a much longer list of options to improve page load performance.

Combobox is built with consideration for the ARIA combobox pattern(link takes you to an external page).

When a user is focused on a Combobox, the listbox opens. When a user makes a selection, the listbox closes so the selected option can be registered to screen readers.

Keyboard interaction

Keyboard interaction page anchor

When the user is focused on a Combobox, the following keyboard interactions apply:

  • Up and down arrows move the user between the options
  • Enter selects the currently active option
  • Escape closes the listbox or clears the selected option if the listbox is already closed

Use a basic Combobox to allow users to select a value from a list of predefined options.

Component preview theme
const authors = ['James Baldwin', 'Adrienne Maree Brown', 'Octavia Butler', 'Ta-Nehisi Coates', 'Audre Lorde', 'Nnedi Okorafor'];
const BasicCombobox = () => {
return (
<Combobox items={authors} initialSelectedItem={authors[2]} labelText="Select an author" required />
);
};
render(
<BasicCombobox />
)

The autocomplete Combobox allows users to filter the list of options by typing into the input directly.

This is useful when a user needs to filter a list of options, or when there are more than 15 options as users may find it difficult to navigate with only scrolling. Pass the autocomplete prop to enable the feature.

Component preview theme
const artists = ['The Aces', 'Brandi Carlile', 'Claud', 'Deb Never', 'Hayley Kiyoko', 'Janelle Monáe', 'LP', 'MUNA', 'Sam Smith', 'Years & Years'];
const AutoCompleteCombobox = () => {
const [inputItems, setInputItems] = React.useState(artists);
return (
<Combobox
autocomplete
items={inputItems}
labelText="Select an artist"
onInputValueChange={({inputValue}) => {
if (inputValue !== undefined) {
setInputItems(artists.filter(item => item.toLowerCase().startsWith(inputValue.toLowerCase())));
}
}}
/>
);
};
render(
<AutoCompleteCombobox />
)

Combobox with add-ons (prefix/suffix text or icons)

Combobox with add-ons (prefix/suffix text or icons) page anchor

Use add-ons to provide users with guidance on formatting their input and to offer more context about the value a user is entering.

  • Prefix/suffix text — Text that can be used as a prefix and/or suffix to the value that is entered. Use prefix/suffix to help users format text.
  • Prefix/suffix icon — Icons can be placed in the same area as the prefix and suffix text. Icons should trigger an action (e.g., clearing a field) or in rare cases, provide further context to what value should be entered to make a field's purpose more immediately visible (e.g., a search icon).
Component preview theme
const numbers = ['(415) 555-CATS', '(415) 555-DOGS', '(415) 555-MICE'];
const PrefixSuffixCombobox = () => {
return (
<Combobox
items={numbers}
labelText="Select a phone number"
insertBefore={<Text color="colorTextWeak" as="span" fontWeight="fontWeightSemibold">+1</Text>}
insertAfter={
<Anchor href="#" display="flex">
<InformationIcon decorative={false} size="sizeIcon20" title="Get more info" />
</Anchor>
}
/>
);
};
render(
<PrefixSuffixCombobox />
)

Combobox with option groups

Combobox with option groups page anchor

Use option groups to create labeled sections of options.

Structure your data into an array of objects and use a key on each object as the grouping identifier. Then, tell the Combobox what you would like to group the items by, by setting groupItemsBy to match the intended group identifier.

In the example below, we have a list of components and we are grouping them based on their type.

Component preview theme
const groupedItems = [
{type: 'Components', label: 'Alert'},
{type: 'Components', label: 'Anchor'},
{type: 'Components', label: 'Button'},
{type: 'Components', label: 'Card'},
{type: 'Components', label: 'Heading'},
{type: 'Components', label: 'List'},
{type: 'Components', label: 'Modal'},
{type: 'Components', label: 'Paragraph'},
{type: 'Primitives', label: 'Box'},
{type: 'Primitives', label: 'Text'},
{type: 'Primitives', label: 'Non-modal dialog'},
{type: 'Layout', label: 'Grid'},
{label: 'Design Tokens'},
];
const GroupedCombobox = () => {
const [inputItems, setInputItems] = React.useState(groupedItems);
return (
<Combobox
autocomplete
groupItemsBy="type"
items={inputItems}
labelText="Choose a component:"
helpText="This is the help text"
optionTemplate={(item) => <div>{item.label}</div>}
onInputValueChange={({inputValue}) => {
if (inputValue !== undefined) {
setInputItems(
filter(groupedItems, (item) => item.label.toLowerCase().startsWith(inputValue.toLowerCase()))
);
}
}}
itemToString={item => (item ? item.label : null)}
/>
);
};
render(
<GroupedCombobox />
)

Combobox with custom group label

Combobox with custom group label page anchor

Expanding on the previous example, it's also possible to customize the group label.

The groupLabelTemplate prop accepts a method with a groupName argument. This method should return valid JSX, which it will render in place of a group label string.

In the example below, we are checking the groupName and rendering an icon next to it based on the name.

Component preview theme
const groupedItems = [
{type: 'Components', label: 'Alert'},
{type: 'Components', label: 'Anchor'},
{type: 'Components', label: 'Button'},
{type: 'Components', label: 'Card'},
{type: 'Components', label: 'Heading'},
{type: 'Components', label: 'List'},
{type: 'Components', label: 'Modal'},
{type: 'Components', label: 'Paragraph'},
{type: 'Primitives', label: 'Box'},
{type: 'Primitives', label: 'Text'},
{type: 'Primitives', label: 'Non-modal Dialog'},
{type: 'Layout', label: 'Grid'},
{label: 'Design tokens'},
];
const GroupedCombobox = () => {
const [inputItems, setInputItems] = React.useState(groupedItems);
return (
<Combobox
autocomplete
groupItemsBy="type"
items={inputItems}
labelText="Choose a component:"
helpText="This is the help text"
optionTemplate={(item) => <div>{item.label}</div>}
groupLabelTemplate={(groupName) => {
if(groupName === 'Components') {
return (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space20">
<ProductStudioIcon color="colorTextIcon" decorative />
</MediaFigure>
<MediaBody>{groupName}</MediaBody>
</MediaObject>
);
}
if(groupName === 'Primitives') {
return (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space20">
<ProductAutopilotIcon color="colorTextIcon" decorative />
</MediaFigure>
<MediaBody>{groupName}</MediaBody>
</MediaObject>
);
}
if(groupName === 'Layout') {
return (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space20">
<ProductInsightsIcon color="colorTextIcon" decorative />
</MediaFigure>
<MediaBody>{groupName}</MediaBody>
</MediaObject>
);
}
return {groupName}
}}
onInputValueChange={({inputValue}) => {
if (inputValue !== undefined) {
setInputItems(
filter(groupedItems, (item) => item.label.toLowerCase().startsWith(inputValue.toLowerCase()))
);
}
}}
itemToString={item => (item ? item.label : null)}
/>
);
};
render(
<GroupedCombobox />
)

Combobox using Option Template

Combobox using Option Template page anchor

Use the option template to display more complex options in a listbox.

The optionTemplate prop allows you to pass jsx to customize the options. Note that we use native HTML input elements to build Combobox and these HTML elements don't allow for images, icons, or svgs to be added even with the option template.

Component preview theme
const months = [
{label: 'November', year: '2020', abbr: 'Nov'},
{label: 'December', year: '2020', abbr: 'Dec'},
{label: 'January', year: '2021', abbr: 'Jan'},
{label: 'February', year: '2021', abbr: 'Feb'},
];
const OptionTemplateCombobox = () => {
return (
<Combobox
items={months}
labelText="Select a month"
optionTemplate={(item) => (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space30">
<CalendarIcon decorative={true} />
</MediaFigure>
<MediaBody>
<Text as="span" fontStyle="italic" color="colorTextWeak">
({item.abbr}){' '}
</Text>
<Text as="span">
{item.label} {item.year}
</Text>
</MediaBody>
</MediaObject>
)}
itemToString={item => (item ? String(item.label) : null)}
/>
);
};
render(
<OptionTemplateCombobox />
)

The Combobox can be used as a controlled component(link takes you to an external page) when you would like full control over your state. Use the properties selectedItem, inputValue, onInputValueChange and onSelectedItemChange to control the value of the Combobox via your own application state.

In the example below the value of the Combobox is stored in a piece of our application state. We update that value based on user input into the Combobox, resetting the value of the Combobox. Upon the user selecting a defined option, we hook into onSelectedItemChange to set our selectedItem state value based on user selection.

Component preview theme
const months = [
{label: 'November', year: '2020', abbr: 'Nov'},
{label: 'December', year: '2020', abbr: 'Dec'},
{label: 'January', year: '2021', abbr: 'Jan'},
{label: 'February', year: '2021', abbr: 'Feb'},
];
const ControlledCombobox = () => {
const [value, setValue] = React.useState('');
const [selectedItem, setSelectedItem] = React.useState();
const [items, setItems] = React.useState(months);
return (
<>
<Combobox
autocomplete
items={items}
labelText="Select a month"
optionTemplate={(item) => (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space30">
<CalendarIcon decorative={true} />
</MediaFigure>
<MediaBody>
<Text as="span" fontStyle="italic" color="colorTextWeak">
({item.abbr}){' '}
</Text>
<Text as="span">
{item.label} {item.year}
</Text>
</MediaBody>
</MediaObject>
)}
itemToString={item => (item ? String(item.label) : null)}
onInputValueChange={({inputValue}) => {
if (inputValue !== undefined) {
setItems(months.filter(item => {
return item.label.toLowerCase().startsWith(inputValue.toLowerCase())
}));
setValue(inputValue);
}
}}
selectedItem={selectedItem}
onSelectedItemChange={changes => {
setSelectedItem(changes.selectedItem);
}}
inputValue={value}
/>
<Box paddingTop="space70">
Input value state: {JSON.stringify(value)}
<br />
Selected item state: {JSON.stringify(selectedItem)}
<br />
Items state: {JSON.stringify(items)}
</Box>
</>
);
};
render(
<ControlledCombobox />
)
(warning)
Power user move!

Only use this property if you are a power user. It's very easy to break your implementation and unfortunately the Paste team will not be able to debug this for you. Proceed with extreme caution.

In addition to being a controlled component, the Combobox comes with the option of "hooking" into the internal state by using the state hook originally provided by Downshift(link takes you to an external page).

Rather than the state be internal to the component, you can use the useCombobox hook and pass the returned state to Combobox as the state prop.

This allows you to destructure certain returned props from the state hook, including action methods like reset.

An example use case of this might be programmatically providing the user a way to clear or reset the Combobox of its previous selections.

It should be noted that when doing so, the state prop takes precident over the other properties that affect the state or initial state of the Combobox. They will be ignored in favour of them being provided as arguments to the useCombobox hook.

For full details on how to use the state hook, and what props to provide it, follow the Combobox Primitive documentation. It's the same hook, just renamed.

Component preview theme
const objectItems = [
{code: 'AD', label: 'Andorra', phone: '376'},
{code: 'AE', label: 'United Arab Emirates', phone: '971'},
{code: 'AF', label: 'Afghanistan', phone: '93'},
{code: 'AG', label: 'Antigua and Barbuda', phone: '1-268'},
{code: 'AI', label: 'Anguilla', phone: '1-264'},
{code: 'AL', label: 'Albania', phone: '355'},
{code: 'AM', label: 'Armenia', phone: '374'},
{code: 'AO', label: 'Angola', phone: '244'},
{code: 'AQ', label: 'Antarctica', phone: '672'},
{code: 'AR', label: 'Argentina', phone: '54'},
{code: 'AS', label: 'American Samoa', phone: '1-684'},
{code: 'AT', label: 'Austria', phone: '44'},
{code: 'BS', label: 'Bahamas', phone: '43'},
{code: 'BH', label: 'Bahrain', phone: '48'},
{code: 'BD', label: 'Bangladesh', phone: '50'},
{code: 'BB', label: 'Barbados', phone: '52'},
{code: 'BY', label: 'Belarus', phone: '112'},
{code: 'BE', label: 'Belgium', phone: '56'},
{code: 'BZ', label: 'Belize', phone: '84'},
{code: 'BJ', label: 'Benin', phone: '204'},
{code: 'BM', label: 'Bermuda', phone: '60'},
{code: 'BT', label: 'Bhutan', phone: '64'},
{code: 'BO', label: 'Bolivia', phone: '68'},
{code: 'BW', label: 'Botswana', phone: '72'},
{code: 'BR', label: 'Brazil', phone: '76'},
{code: 'KH', label: 'Cambodia', phone: '116'},
{code: 'CA', label: 'Canada', phone: '124'},
];
const ComboboxControlledUsingState = () => {
const [value, setValue] = React.useState('');
const [selectedItem, setSelectedItem] = React.useState({});
const [inputItems, setInputItems] = React.useState(objectItems);
const {reset, ...state} = useCombobox({
items: inputItems,
itemToString: (item) => (item ? item.label : ''),
onSelectedItemChange: (changes) => {
if (changes.selectedItem != null) {
setSelectedItem(changes.selectedItem);
}
},
onInputValueChange: ({inputValue}) => {
if (inputValue !== undefined) {
setInputItems(
filter(objectItems, (item) => item.label.toLowerCase().startsWith(inputValue.toLowerCase()))
);
setValue(inputValue);
}
},
initialInputValue: value,
initialSelectedItem: selectedItem,
});
return (
<>
<Combobox
state={{...state, reset}}
items={inputItems}
autocomplete
itemToString={(item) => (item ? item.label : '')}
labelText="Choose a country:"
helpText="This is the help text"
optionTemplate={(item) => (
<div>
{item.code} | {item.label} | {item.phone}
</div>
)}
insertAfter={
<Button
variant="link"
size="reset"
onClick={() => {
setValue('');
setSelectedItem({});
reset();
}}
>
{!!value ? <CloseIcon decorative={false} title="Clear" /> : <SearchIcon decorative={false} title="Search" />}
</Button>
}
/>
<Box paddingTop="space70">
Input value state: {JSON.stringify(value)}
<br />
Selected item state: {JSON.stringify(selectedItem)}
</Box>
</>
);
};
render(
<ComboboxControlledUsingState />
)
Component preview theme
const products = ['SMS', 'Phone Numbers', 'Video'];
const DisabledCombobox = () => {
return (
<Combobox items={products} labelText="Select a product" disabled />
);
};
render(
<DisabledCombobox />
)

Combobox with disabled options

Combobox with disabled options page anchor
Component preview theme
const products = ['SMS', 'Fax', 'Phone Numbers', 'Video', 'Email', 'Chat'];
const DisabledCombobox = () => {
return (
<Combobox items={products} labelText="Select a product" disabledItems={products.slice(1,3)} />
);
};
render(
<DisabledCombobox />
)

Combobox with inline error

Combobox with inline error page anchor
Component preview theme
const products = ['SMS', 'Phone Numbers', 'Video'];
const ErrorCombobox = () => {
return (
<Combobox items={products} labelText="Select a product" helpText="This is the error message" hasError />
);
};
render(
<ErrorCombobox />
)

Combobox with an empty state

Combobox with an empty state page anchor

Use an empty state to indicate to a user that their input does not match any value in the list of options.

Component preview theme
const authors = ['James Baldwin', 'Adrienne Maree Brown', 'Octavia Butler', 'Ta-Nehisi Coates', 'Audre Lorde', 'Nnedi Okorafor'];
const SampleEmptyState = () => (
<Box paddingY="space40" paddingX="space50">
<Text as="span" fontStyle="italic" color="colorTextWeak">
No results found
</Text>
</Box>
);
const EmptyStateCombobox = () => {
const [inputItems, setInputItems] = React.useState([]);
return (
<Combobox
autocomplete
items={inputItems}
inputValue="test123"
onInputValueChange={({inputValue}) => {
if (inputValue !== undefined) {
setInputItems(items.filter((item) => item.toLowerCase().startsWith(inputValue.toLowerCase())));
}
}}
emptyState={SampleEmptyState}
labelText="Select an author"
helpText="Try searching for an item that doesn't exist in the list."
/>
);
};
render(
<EmptyStateCombobox />
)

A Combobox consists of a label, an input and a listbox.

Stack form fields vertically with $space-80 spacing between each field. Avoid placing multiple form fields on the same horizontal row to help make it easier to scan a page vertically.

Keep option text concise and simple.

If setting a default selected value, use a safe and reversible option as the default selected value.

Use at least 7 options in a Combobox. If there are less than 7 options or if choosing options requires more explanation, consider using a Radio Group instead and add help text for each option, or give more explanation through help text. Sort options logically (e.g., alphabetically, by value) or in an order that’s intuitive to your user.

Use help text to provide information to help users avoid errors.

Use Help Text to show an inline error text beneath the combobox to explain how to fix an error. For additional guidance on how to compose error messages, refer to the error state pattern.