Skip to contentSkip to navigationSkip to topbar
Figma
Star

Multiselect Combobox

Version 16.2.1GithubStorybook

A Multiselect Combobox is a styled dropdown form element that allows users to select multiple values from a list.

Component preview theme
const items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const SampleEmptyState = () => (
<Box paddingY="space40" paddingX="space50">
<Text as="span" fontStyle="italic" color="colorTextWeak">
No results found
</Text>
</Box>
);
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
items={filteredItems}
initialSelectedItems={items.slice(1, 3)}
emptyState={SampleEmptyState}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

Guidelines

Guidelines page anchor

About Multiselect Combobox

About Multiselect Combobox page anchor

A Multiselect Combobox allows a user to either type or select multiple values from a styled listbox of options. Each option can consist of more than just text, e.g. text paired with an icon.

What’s the difference between Select and Comboboxes?

What’s the difference between Select and Comboboxes? 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 multiselect.

Use a Select when:

  • You need a native picker experience, especially on mobile devices.
  • 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).
  • You need the component to work out-of-the-box across all operating systems and browsers.

Use a Multiselect 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.

Multiselect 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

When the user is focused within the Form Pill Group, they can use these keyboard interactions:

  • Left and right arrow keys move focus within the group.
  • If a pill is selectable, spacebar and enter will toggle pill selection.
  • If a pill is dismissible, the pill can be removed by pressing the delete or backspace key.

Basic Multiselect Combobox

Basic Multiselect Combobox page anchor

Use a basic Multiselect Combobox to allow users to select multiple values from a list of predefined options.

The height of the Combobox field will increase to fit the selection of Form Pills. Optionally, you may set a max height using the maxHeight prop and if there are more pills than viewable at max height, users can vertically scroll to view all the selected options.

Component preview theme
const items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const SampleEmptyState = () => (
<Box paddingY="space40" paddingX="space50">
<Text as="span" fontStyle="italic" color="colorTextWeak">
No results found
</Text>
</Box>
);
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
items={filteredItems}
initialSelectedItems={items.slice(1, 3)}
emptyState={SampleEmptyState}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

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

Multiselect 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 items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
items={filteredItems}
initialSelectedItems={items.slice(1, 3)}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
insertBefore={
<Text color="colorTextWeak" as="span" fontWeight="fontWeightSemibold">
$10.99
</Text>
}
insertAfter={
<Anchor href="#" display="flex">
<InformationIcon decorative={false} size="sizeIcon20" title="Get more info" />
</Anchor>
}
/>
);
}
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with option groups

Multiselect 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 = [
{group: 'Components', label: 'Alert'},
{group: 'Components', label: 'Anchor'},
{group: 'Components', label: 'Button'},
{group: 'Components', label: 'Card'},
{group: 'Components', label: 'Heading'},
{group: 'Components', label: 'List'},
{group: 'Components', label: 'Modal'},
{group: 'Components', label: 'Paragraph'},
{group: 'Primitives', label: 'Box'},
{group: 'Primitives', label: 'Text'},
{group: 'Primitives', label: 'Non-modal dialog'},
{group: 'Layout', label: 'Grid'},
{label: 'Design Tokens'},
];
function getFilteredGroupedItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return filter(groupedItems, (item) => item.label.toLowerCase().includes(lowerCasedInputValue));
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredGroupedItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
groupItemsBy="group"
items={filteredItems}
itemToString={(item) => (item ? item.label : '')}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
optionTemplate={(item) => <div>{item.label}</div>}
/>
);
}
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with a custom group label

Multiselect Combobox with a 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 = [
{group: 'Components', label: 'Alert'},
{group: 'Components', label: 'Anchor'},
{group: 'Components', label: 'Button'},
{group: 'Components', label: 'Card'},
{group: 'Components', label: 'Heading'},
{group: 'Components', label: 'List'},
{group: 'Components', label: 'Modal'},
{group: 'Components', label: 'Paragraph'},
{group: 'Primitives', label: 'Box'},
{group: 'Primitives', label: 'Text'},
{group: 'Primitives', label: 'Non-modal dialog'},
{group: 'Layout', label: 'Grid'},
{label: 'Design Tokens'},
];
function getFilteredGroupedItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return filter(groupedItems, (item) => item.label.toLowerCase().includes(lowerCasedInputValue));
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredGroupedItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
groupItemsBy="group"
items={filteredItems}
itemToString={(item) => (item ? item.label : '')}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
optionTemplate={(item) => <div>{item.label}</div>}
groupLabelTemplate={(groupName) => {
if (groupName === 'Components') {
return (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space20">
<AttachIcon color="colorTextIcon" decorative={false} title="icon" />
</MediaFigure>
<MediaBody>{groupName}</MediaBody>
</MediaObject>
);
}
return groupName;
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with option template

Multiselect Combobox with 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 components = [
{group: 'Components', label: 'Alert'},
{group: 'Components', label: 'Anchor'},
{group: 'Components', label: 'Button'},
{group: 'Components', label: 'Card'},
{group: 'Components', label: 'Heading'},
{group: 'Components', label: 'List'},
{group: 'Components', label: 'Modal'},
{group: 'Components', label: 'Paragraph'},
{group: 'Primitives', label: 'Box'},
{group: 'Primitives', label: 'Text'},
{group: 'Primitives', label: 'Non-modal dialog'},
{group: 'Layout', label: 'Grid'},
];
function getFilteredComponents(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return components.filter(function filterComponents(component) {
return (
component.group.toLowerCase().includes(lowerCasedInputValue) ||
component.label.toLowerCase().includes(lowerCasedInputValue)
);
});
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredComponents(inputValue), [inputValue]);
return (
<MultiselectCombobox
labelText="Choose a component"
selectedItemsLabelText="Selected components:"
helpText="Paste components are the building blocks of your product UI."
items={filteredItems}
itemToString={(item) => (item ? item.label : '')}
optionTemplate={({label, group}) => (
<Box as="span" display="flex" flexDirection="column">
<Box as="span">{label}</Box>
<Box as="span" color="colorTextWeak">{group}</Box>
</Box>
)}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
};
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with max height

Multiselect Combobox with max height page anchor

By default the Multiselect Combobox will grow to fit the content inside it. If you want to limit how much it resizes vertically, you can provide a maxHeight prop.

Component preview theme
const items = Array.from(new Array(100)).map((_,index) => `Item ${index}`)
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const SampleEmptyState = () => (
<Box paddingY="space40" paddingX="space50">
<Text as="span" fontStyle="italic" color="colorTextWeak">
No results found
</Text>
</Box>
);
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
maxHeight="130px"
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Try searching for an item that doesn't exist in the list."
items={filteredItems}
initialSelectedItems={items.slice(20, 80)}
emptyState={SampleEmptyState}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

useMultiselectCombobox state hook

useMultiselectCombobox state hook page anchor
(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 Multiselect 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 useMultiselectCombobox hook and pass the returned state to MultiselectCombobox 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 Multiselect 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 MultiselectCombobox. They will be ignored in favour of them being provided as arguments to the useMultiselectCombobox 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 groupedItems = [
{group: 'Components', label: 'Alert'},
{group: 'Components', label: 'Anchor'},
{group: 'Components', label: 'Button'},
{group: 'Components', label: 'Card'},
{group: 'Components', label: 'Heading'},
{group: 'Components', label: 'List'},
{group: 'Components', label: 'Modal'},
{group: 'Components', label: 'Paragraph'},
{group: 'Primitives', label: 'Box'},
{group: 'Primitives', label: 'Text'},
{group: 'Primitives', label: 'Non-modal dialog'},
{group: 'Layout', label: 'Grid'},
{label: 'Design Tokens'},
];
function getFilteredGroupedItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return filter(groupedItems, (item) => item.label.toLowerCase().includes(lowerCasedInputValue));
}
const SampleEmptyState = () => (
<Box paddingY="space40" paddingX="space50">
<Text as="span" fontStyle="italic" color="colorTextWeak">
No results found
</Text>
</Box>
);
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredGroupedItems(inputValue), [inputValue]);
const onSelectedItemsChange = React.useCallback((selectedItems) => {
console.log(selectedItems);
}, []);
const state = useMultiselectCombobox({
initialSelectedItems: filteredItems.slice(0, 2),
onSelectedItemsChange,
});
return (
<>
<Box marginBottom="space40">
<Button variant="primary" onClick={() => state.setSelectedItems([])}>
Clear selection
</Button>
</Box>
<MultiselectCombobox
state={state}
groupItemsBy="group"
items={filteredItems}
emptyState={SampleEmptyState}
inputValue={inputValue}
itemToString={(item) => (item ? item.label : '')}
onInputValueChange={({inputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={onSelectedItemsChange}
labelText="Choose a Paste Component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
initialIsOpen
optionTemplate={(item) => {
return <div>{item.label}</div>;
}}
groupLabelTemplate={(groupName) => {
if (groupName === 'Components') {
return (
<MediaObject verticalAlign="center">
<MediaFigure spacing="space20">
<AttachIcon color="colorTextIcon" decorative={false} title="icon" />
</MediaFigure>
<MediaBody>{groupName}</MediaBody>
</MediaObject>
);
}
return groupName;
}}
/>
</>
);
}
render(
<MultiselectComboboxExample />
)

Disabled Multiselect Combobox

Disabled Multiselect Combobox page anchor
Component preview theme
const items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
disabled
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
items={filteredItems}
initialSelectedItems={items.slice(1, 3)}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with disabled options

Multiselect Combobox with disabled options page anchor
Component preview theme
const items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Paste components are the building blocks of your product UI."
items={filteredItems}
disabledItems={filteredItems.slice(2, 4)}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with error

Multiselect Combobox with error page anchor
Component preview theme
const items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
hasError
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Tooltip must be used with an anchor or button."
items={filteredItems}
initialSelectedItems={items.slice(1, 3)}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

Multiselect Combobox with an empty state

Multiselect 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 items = [
'Alert',
'Anchor',
'Button',
'Card',
'Heading',
'List',
'Modal',
'Paragraph',
'Popover',
'Tooltip',
];
function getFilteredItems(inputValue) {
const lowerCasedInputValue = inputValue.toLowerCase();
return items.filter(function filterItems(item) {
return item.toLowerCase().includes(lowerCasedInputValue);
});
}
const SampleEmptyState = () => (
<Box paddingY="space40" paddingX="space50">
<Text as="span" fontStyle="italic" color="colorTextWeak">
No results found
</Text>
</Box>
);
const MultiselectComboboxExample = () => {
const [inputValue, setInputValue] = React.useState('');
const filteredItems = React.useMemo(() => getFilteredItems(inputValue), [inputValue]);
return (
<MultiselectCombobox
emptyState={SampleEmptyState}
labelText="Choose a Paste component"
selectedItemsLabelText="Selected Paste components"
helpText="Try searching for an item that doesn't exist in the list."
items={filteredItems}
onInputValueChange={({inputValue: newInputValue = ''}) => {
setInputValue(newInputValue);
}}
onSelectedItemsChange={(selectedItems) => {
console.log(selectedItems);
}}
/>
);
}
render(
<MultiselectComboboxExample />
)

A Multiselect Combobox is comprised 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. Option text will truncate when it’s longer than the width of the container. 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 Checkbox 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.

When to use Multiselect Combobox

When to use Multiselect Combobox page anchor
Do

Use a Multiselect Combobox when there are multiple options for a user to type or select.

Don't

Don’t use a Multiselect Combobox when a user should only select a single option. Use a Singleselect Combobox instead.

Do

Use a Multiselect Combobox when there are at least 7 options for a user to type or select from.

Don't

Don’t use a Multiselect Combobox if there are less than 7 options in a list for users to select multiple values from. Use a Checkbox Group instead.