Skip to contentSkip to navigationSkip to topbar
Figma
Star

Combobox Primitive

Version 2.1.1GithubStorybook

An unstyled and accessible basis upon which to build a combobox.


Component preview theme
const items = ['Alert', 'Anchor', 'Button', 'Card', 'Heading', 'List', 'Modal', 'Paragraph'];
const BasicCombobox = () => {
const {
getComboboxProps,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,
highlightedIndex,
isOpen,
selectedItem,
} = useComboboxPrimitive({items});
const uid = useUID();
return (
<>
<Label htmlFor={uid} {...getLabelProps()}>
Choose a component:
</Label>
<Box {...getComboboxProps({role: 'combobox'})}>
<Input
id={uid}
type="text"
{...getInputProps(getToggleButtonProps({tabIndex: 0}))}
value={selectedItem || ''}
/>
</Box>
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li
style={highlightedIndex === index ? {textDecoration: 'underline'} : {}}
key={item}
{...getItemProps({item, index})}
>
{item}
</li>
))}
</ul>
</>
);
};
render(
<BasicCombobox />
)

Guidelines

Guidelines page anchor

About the Combobox Primitive

About the Combobox Primitive page anchor

This package provides a foundation upon which developers can implement a WAI-Aria compliant Combobox(link takes you to an external page). It can be used to build functional and accessible Comboboxes with or without autocomplete/typeahead features. Our Combobox is built on top of this primitive.

The purpose of providing these unstyled primitives is to cater for instances when the styled Combobox provided by Paste, doesn't meet the requirements needed to solve a unique or individual customer problem. At that point you are welcome to fallback to this functional primitive to roll your own styled Combobox whilst still providing a functional and accessible experience to your customers.

This primitive should be used to compose all custom Comboboxes to ensure accessibility and upgrade paths.

(warning)

Warning

We strongly suggest that all components built on top of this primitive get reviewed by the Design Systems team and goes through the UX Review process to ensure an excellent experience for our customers.

A basic Combobox is a direct replacement for the native HTML select element. It should function in roughly the same way, the difference being in the ability to style everything about it; the input trigger, option list and option content.

Contrary to the Downshift documentation and example, the basic Combobox should have an HTML input element as the trigger, and not an HTML button. Use an input element and set the role of Combobox in the getToggleButtonProps getter or directly on the element itself. You can see this demonstrated below:

(information)

Using an input element prevents screen readers like JAWS switching out of focus or forms mode when navigating through a form. This provides a really frustrating experience to users of JAWS. It is also a direct replacement for a form element, so the value should be accessible via the form submit event, which a button element will not provide.

Component preview theme
const items = ['Alert', 'Anchor', 'Button', 'Card', 'Heading', 'List', 'Modal', 'Paragraph'];
const BasicCombobox = () => {
const {
getComboboxProps,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,
highlightedIndex,
isOpen,
selectedItem,
} = useComboboxPrimitive({items});
const uid = useUID();
return (
<>
<Label htmlFor={uid} {...getLabelProps()}>
Choose a component:
</Label>
<Box {...getComboboxProps({role: 'combobox'})}>
<Input
id={uid}
type="text"
{...getInputProps(getToggleButtonProps({tabIndex: 0}))}
value={selectedItem || ''}
/>
</Box>
<ul {...getMenuProps()}>
{isOpen &&
items.map((item, index) => (
<li
style={highlightedIndex === index ? {textDecoration: 'underline'} : {}}
key={item}
{...getItemProps({item, index})}
>
{item}
</li>
))}
</ul>
</>
);
};
render(
<BasicCombobox />
)

Autocomplete Combobox Example

Autocomplete Combobox Example page anchor

This hook can be used to create custom autocomplete Combobox controls. These controls are useful when the customer needs to filter a list of available options, or provide a custom free form value to the input.

Component preview theme
const items = ['Alert', 'Anchor', 'Button', 'Card', 'Heading', 'List', 'Modal', 'Paragraph'];
const AutocompleteCombobox = () => {
const [inputItems, setInputItems] = React.useState(items);
const {
getComboboxProps,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,
highlightedIndex,
isOpen,
} = useComboboxPrimitive({
items: inputItems,
onInputValueChange: ({inputValue}) => {
setInputItems(items.filter(item => item.toLowerCase().startsWith(inputValue.toLowerCase())));
},
});
const uid = useUID();
return (
<>
<Label htmlFor={uid} {...getLabelProps()}>
Choose a component:
</Label>
<Box display="flex" {...getComboboxProps()}>
<Input id={uid} type="text" {...getInputProps()} />
<Button {...getToggleButtonProps()} aria-label="toggle menu" variant="primary">
<ChevronDownIcon size="sizeIcon30" decorative={false} title="toggle menu" />
</Button>
</Box>
<ul {...getMenuProps()}>
{isOpen &&
inputItems.map((item, index) => (
<li
style={highlightedIndex === index ? {textDecoration: 'underline'} : {}}
key={item}
{...getItemProps({item, index})}
>
{item}
</li>
))}
</ul>
</>
);
};
render(
<AutocompleteCombobox />
)

Multiselect Combobox Example

Multiselect Combobox Example page anchor
Component preview theme
const items = ['Alert', 'Anchor', 'Button', 'Card', 'Heading', 'List', 'Modal', 'Paragraph'];
const BasicMultiCombobox = () => {
const seed = useUIDSeed();
const [filteredItems, setFilteredItems] = React.useState([...items]);
const formPillState = useFormPillState();
const {
getSelectedItemProps,
getDropdownProps,
addSelectedItem,
removeSelectedItem,
selectedItems,
} = useMultiSelectPrimitive({});
const handleSelectItemOnClick = React.useCallback(
(selectedItem) => {
addSelectedItem(selectedItem);
setFilteredItems((currentFilteredItems) => currentFilteredItems.filter((item) => item !== selectedItem));
},
[addSelectedItem, setFilteredItems]
);
const handleRemoveItemOnClick = React.useCallback(
(selectedItem) => {
removeSelectedItem(selectedItem);
setFilteredItems((currentFilteredItems) => [...currentFilteredItems, selectedItem].sort());
},
[removeSelectedItem]
);
const {
getComboboxProps,
getInputProps,
getItemProps,
getLabelProps,
getMenuProps,
getToggleButtonProps,
highlightedIndex,
isOpen,
selectedItem,
selectItem,
} = useComboboxPrimitive({
items: filteredItems,
initialInputValue: '',
onSelectedItemChange: ({selectedItem: selected}) => {
if (selected != null) {
handleSelectItemOnClick(selected);
}
selectItem(null);
},
});
const inputId = seed('input-element');
return (
<>
<Box marginBottom="space40" position="relative">
<Label htmlFor={inputId} {...getLabelProps()}>
Choose a component
</Label>
<Box {...getComboboxProps({role: 'combobox'})}>
<Input
id={inputId}
type="text"
{...getInputProps({
...getDropdownProps({
preventKeyAction: isOpen,
...getToggleButtonProps({tabIndex: 0}),
}),
})}
value={selectedItem || ''}
/>
</Box>
<ComboboxListbox hidden={!isOpen} {...getMenuProps()}>
<ComboboxListboxGroup>
{filteredItems.map((filteredItem, index) => (
<ComboboxListboxOption
highlighted={highlightedIndex === index}
variant="default"
{...getItemProps({item: filteredItem, index, key: seed('filtered-item-' + filteredItem)})}
>
{filteredItem}
</ComboboxListboxOption>
))}
</ComboboxListboxGroup>
</ComboboxListbox>
</Box>
<FormPillGroup {...formPillState} aria-label="Selected components">
{selectedItems.map((item, index) => {
return (
<FormPill
{...getSelectedItemProps({
selectedItem,
index,
key: 'selected-item-' + item,
})}
tabIndex={null}
{...formPillState}
onDismiss={() => handleRemoveItemOnClick(item)}
>
{item}
</FormPill>
);
})}
</FormPillGroup>
</>
);
};
render(
<BasicMultiCombobox />
)

This package is a wrapper around the Downshift package(link takes you to an external page). Our wrapper currently only exposes the useCombobox hook, but renamed for Paste. The reason we chose to just expose the hook is that we feel it is the most flexible way of consuming downshift and better fit our chosen styling model.

If you’re wondering why we wrapped that package into our own, we reasoned that it would be best for our consumers' developer experience. For example:

  • If we want to migrate the underlying nuts and bolts in the future, Twilio products that depend on this primitive would need to replace all occurrences of import … from ‘x-package’ to import … from ‘@some-new/package’. By wrapping it in @twilio-paste/x-primitive, this refactor can be avoided. The only change would be a version bump in the package.json file for the primitive.
  • We can more strictly enforce semver and backwards compatibility than some of our dependencies.
  • We can control when to provide an update and which versions we allow, to help reduce potential bugs our consumers may face.
  • We can control which APIs we expose. For example, we may chose to enable or disable usage of certain undocumented APIs.
yarn add @twilio-paste/combobox-primitive - or - yarn add @twilio-paste/core

useComboboxPrimitive basic props

useComboboxPrimitive basic props page anchor

This is the list of props that you should probably know about. There are some advanced props below as well.

items any[] | required

The main difference from vanilla Downshift is that we pass the items we want to render to the hook as well. Opening the menu with an item already selected means the hook has to know in advance what items you plan to render and what is the position of that item in the list. Consequently, there won't be any need for two state changes: one for opening the menu and one for setting the highlighted index, like in Downshift.

itemToString function(item: any) | defaults to: item => (item ? String(item) : '')

If your items are stored as, say, objects instead of strings, downshift still needs a string representation for each one. This is required for accessibility messages (e.g., after making a selection).

Note: This callback must include a null check: it is invoked with null whenever the user abandons input via <Esc>.

onSelectedItemChange function(changes: object) | optional, no useful default

Called each time the selected item was changed. Selection can be performed by item click, Enter Key while item is highlighted or by blurring the menu while an item is highlighted (Tab, Shift-Tab or clicking away).

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the selectedItem property with the newly selected value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a selectedItem change, for example useCombobox.stateChangeTypes.ItemClick.
stateReducer function(state: object, actionAndChanges: object) | optional

This is a really handy power feature

This function will be called each time useCombobox sets its internal state (or calls your onStateChange handler for control props). It allows you to modify the state change that will take place which can give you fine grain control over how the component interacts with user updates. It gives you the current state and the state that will be set, and you return the state that you want to set.

  • state: The full current state of downshift.
  • actionAndChanges: Object that contains the action type, props needed to return a new state based on that type and the changes suggested by the Downshift default reducer. About the type property you can learn more about in the stateChangeTypes section.

useCombobox advanced props

useCombobox advanced props page anchor
initialSelectedItem any | defaults to null

Pass an item that should be selected when downshift is initialized.

initialIsOpen boolean | defaults to false

Pass a boolean that sets the open state of the menu when downshift is initialized.

initialHighlightedIndex number | defaults to -1

Pass a number that sets the index of the highlighted item when downshift is initialized.

initialInputValue string | defaults to ''

Pass a string that sets the content of the input when downshift is initialized.

defaultSelectedItem any | defaults to null

Pass an item that should be selected when downshift is reset.

defaultIsOpen boolean | defaults to false

Pass a boolean that sets the open state of the menu when downshift is reset or when an item is selected.

defaultHighlightedIndex number | defaults to -1

Pass a number that sets the index of the highlighted item when downshift is reset or when an item is selected.

defaultInputValue string | defaults to ''

Pass a string that sets the content of the input when downshift is reset or when an item is selected.

getA11yStatusMessage function({/* see below */}) | default messages provided in English

This function is passed as props to a status updating function nested within that allows you to create your own ARIA statuses. It is called when one of the following props change: items, highlightedIndex, inputValue or isOpen.

A default getA11yStatusMessage function is provided that will check resultCount and return "No results are available." or if there are results , "resultCount results are available, use up and down arrow keys to navigate. Press Enter key to select."

getA11ySelectionMessage function({/* see below */}) | default messages provided in English

This function is similar to the getA11yStatusMessage but it is generating a message when an item is selected. It is passed as props to a status updating function nested within that allows you to create your own ARIA statuses. It is called when selectedItem changes.

A default getA11ySelectionMessage function is provided. When an item is selected, the message is a selection related one, narrating "itemToString(selectedItem) has been selected".

The object you are passed to generate your status message, for both getA11yStatusMessage and getA11ySelectionMessage, has the following properties:

propertytypedescription
highlightedIndexnumberThe currently highlighted index
highlightedItemanyThe value of the highlighted item
isOpenbooleanThe isOpen state
inputValuestringThe value in the text input.
itemToStringfunction(any)The itemToString function (see props) for getting the string value from one of the options
previousResultCountnumberThe total items showing in the dropdown the last time the status was updated
resultCountnumberThe total items showing in the dropdown
selectedItemanyThe value of the currently selected item
onHighlightedIndexChange function(changes: object) | optional, no useful default

Called each time the highlighted item was changed. Items can be highlighted while hovering the mouse over them or by keyboard keys such as Up Arrow, Down Arrow, Home and End. Arrow keys can be combined with Shift to move by a step of 5 positions instead of 1. Items can also be highlighted by hitting character keys that are part of their starting string equivalent.

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the highlightedIndex property with the new value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a highlightedIndex change, for example useCombobox.stateChangeTypes.MenuKeyDownArrowUp.
onIsOpenChange function(changes: object) | optional, no useful default

Called each time the menu is open or closed. Menu can be open by toggle button click, Enter, Space, Up Arrow or Down Arrow keys. Can be closed by selecting an item, blur (Tab, Shift-Tab or clicking outside), clicking the toggle button again or hitting Escape key.

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the isOpen property with the new value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a isOpen change, for example useCombobox.stateChangeTypes.ToggleButtonClick.
onInputValueChange function(changes: object) | optional, no useful default

Called each time the value in the input text changes. The input value should change like any input of type text, at any character key press, Space, Backspace, Escape etc.

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the inputValue property with the new value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a inputValue change, for example useCombobox.stateChangeTypes.InputChange.
onStateChange function(changes: object) | optional, no useful default

This function is called anytime the internal state changes. This can be useful if you're using downshift as a "controlled" component, where you manage some or all of the state (e.g., isOpen, selectedItem, highlightedIndex, etc) and then pass it as props, rather than letting downshift control all its state itself.

  • changes: These are the properties that actually have changed since the last state change. This also has a type property which you can learn more about in the stateChangeTypes section.
highlightedIndex number

The index of the item that should be highlighted when menu is open.

isOpen boolean

The open state of the menu.

selectedItem any

The item that should be selected.

inputValue string

The value to be displayed in the text input.

id string | defaults to a generated ID

Used to generate the first part of the Downshift id on the elements. You can override this id with one of your own, provided as a prop, or you can override the id for each element altogether using the props below.

labelId string | defaults to a generated ID

Used for aria attributes and the id prop of the element (label) you use getLabelProps with.

menuId string | defaults to a generated ID

Used for aria attributes and the id prop of the element (ul) you use getMenuProps with.

toggleButtonId string | defaults to a generated ID

Used for aria attributes and the id prop of the element (button) you use getToggleButtonProps with.

inputId string | defaults to a generated ID

Used for aria attributes and the id prop of the element (input) you use getInputProps with.

getItemId function(index) | defaults to a function that generates an ID based on the index

Used for aria attributes and the id prop of the element (li) you use getItemProps with.

environment window | defaults to window

This prop is only useful if you're rendering downshift within a different window context from where your JavaScript is running; for example, an iframe or a shadow-root. If the given context is lacking document and/or add|removeEventListener on its prototype (as is the case for a shadow-root) then you will need to pass in a custom object that is able to provide access to these properties(link takes you to an external page) for downshift.

circularNavigation boolean | defaults to true

Controls the circular keyboard navigation between items. If set to true, when first item is highlighted, the Arrow Up will move highlight to the last item, and viceversa using Arrow Down.

useComboboxPrimitive state change types

useComboboxPrimitive state change types page anchor

There are a few props that expose changes to state (onStateChange and stateReducer). For you to make the most of these APIs, it's important for you to understand why state is being changed. To accomplish this, there's a type property on the changes object you get. This type corresponds to a stateChangeTypes property.

The list of all possible values this type property can take is defined in [this file][state-change-file] and is as follows:

  • useCombobox.stateChangeTypes.InputKeyDownArrowDown
  • useCombobox.stateChangeTypes.InputKeyDownArrowUp
  • useCombobox.stateChangeTypes.InputKeyDownEscape
  • useCombobox.stateChangeTypes.InputKeyDownHome
  • useCombobox.stateChangeTypes.InputKeyDownEnd
  • useCombobox.stateChangeTypes.InputKeyDownEnter
  • useCombobox.stateChangeTypes.InputChange
  • useCombobox.stateChangeTypes.InputBlur
  • useCombobox.stateChangeTypes.MenuMouseLeave
  • useCombobox.stateChangeTypes.ItemMouseMove
  • useCombobox.stateChangeTypes.ItemClick
  • useCombobox.stateChangeTypes.ToggleButtonClick
  • useCombobox.stateChangeTypes.FunctionToggleMenu
  • useCombobox.stateChangeTypes.FunctionOpenMenu
  • useCombobox.stateChangeTypes.FunctionCloseMenu
  • useCombobox.stateChangeTypes.FunctionSetHighlightedIndex
  • useCombobox.stateChangeTypes.FunctionSelectItem
  • useCombobox.stateChangeTypes.FunctionSetInputValue
  • useCombobox.stateChangeTypes.FunctionReset

useComboboxPrimitive control props

useComboboxPrimitive control props page anchor

Downshift manages its own state internally and calls your onSelectedItemChange, onIsOpenChange, onHighlightedIndexChange, onInputChange and onStateChange handlers with any relevant changes. The state that downshift manages includes: isOpen, selectedItem, inputValue and highlightedIndex. Returned action function (read more below) can be used to manipulate this state and can likely support many of your use cases.

However, if more control is needed, you can pass any of these pieces of state as a prop (as indicated above) and that state becomes controlled. As soon as this.props[statePropKey] !== undefined, internally, downshift will determine its state based on your prop's value rather than its own internal state. You will be required to keep the state up to date (this is where onStateChange comes in really handy), but you can also control the state from anywhere, be that state from other components, redux, react-router, or anywhere else.

Note: This is very similar to how normal controlled components work elsewhere in react (like <input />). If you want to learn more about this concept, you can learn about that from this the Advanced React Component Patterns course

useComboboxPrimitive returned

useComboboxPrimitive returned page anchor
useComboboxPrimitive prop getters
useComboboxPrimitive prop getters page anchor
(information)
On prop getters and accessibility

These prop-getters provide `aria-` attributes which are very important > to your component being accessible. It's recommended that you utilize these > functions and apply the props they give you to your components.

getLabelProps
getLabelProps page anchor

This method should be applied to the label you render. It will generate an id that will be used to label the toggle button and the menu.

There are no required properties for this method.

Note: For accessibility purposes, calling this method is highly recommended.

This method should be applied to the element which contains your list of items. Typically, this will be a <div> or a <ul> that surrounds a map expression. This handles the proper ARIA roles and attributes.

Optional properties:

  • refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getMenuProps({refKey: 'innerRef'}) and your composite component would forward like: <ul ref={props.innerRef} />. However, if you are just rendering a primitive component like <div>, there is no need to specify this property. It defaults to ref.

    Please keep in mind that menus, for accessiblity purposes, should always be rendered, regardless of whether you hide it or not. Otherwise, getMenuProps may throw error if you unmount and remount the menu.

  • aria-label: By default the menu will add an aria-labelledby that refers to the <label> rendered with getLabelProps. However, if you provide aria-label to give a more specific label that describes the options available, then aria-labelledby will not be provided and screen readers can use your aria-label instead.

In some cases, you might want to completely bypass the refKey check. Then you can provide the object {suppressRefError : true} as the second argument to getMenuProps. Please use it with extreme care and only if you are absolutely sure that the ref is correctly forwarded otherwise useCombobox will unexpectedly fail.

Note that for accessibility reasons it's best if you always render this element whether or not downshift is in an isOpen state.

The props returned from calling this function should be applied to any menu items you render.

This is an impure function, so it should only be called when you will actually be applying the props to an item.

Required properties:

The main difference from vanilla Downshift is that we require the items as props before rendering. The reason is to open the menu with items already highlighted, and we need to know the items before the actual render. It is still required to pass either item or index to getItemProps.

  • item: this is the item data that will be selected when the user selects a particular item.
  • index: This is how downshift keeps track of your item when updating the highlightedIndex as the user keys around. By default, downshift will assume the index is the order in which you're calling getItemProps. This is often good enough, but if you find odd behavior, try setting this explicitly. It's probably best to be explicit about index when using a windowing library like react-virtualized.

Optional properties:

  • ref: if you need to access the item element via a ref object, you'd call the function like this: getItemProps({ref: yourItemRef}). As a result, the item element will receive a composed ref property, which guarantees that both your code and useCombobox use the same correct reference to the element.

  • refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getItemProps({refKey: 'innerRef'}) and your composite component would forward like: <li ref={props.innerRef} />. However, if you are just rendering a primitive component like <div>, there is no need to specify this property. It defaults to ref.

  • disabled: If this is set to true, then all of the downshift item event handlers will be omitted. Items will not be highlighted when hovered, and items will not be selected when clicked.

Call this and apply the returned props to a button. It allows you to toggle the Menu component.

Optional properties:

  • ref: if you need to access the button element via a ref object, you'd call the function like this: getToggleButton({ref: yourButtonRef}). As a result, the button element will receive a composed ref property, which guarantees that both your code and useCombobox use the same correct reference to the element.

  • refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getToggleButton({refKey: 'innerRef'}) and your composite component would forward like: <button ref={props.innerRef} />. However, if you are just rendering a primitive component like <div>, there is no need to specify this property. It defaults to ref.

  • disabled: If this is set to true, then all of the downshift button event handlers will be omitted (it won't toggle the menu when clicked).

This method should be applied to the input you render. It is recommended that you pass all props as an object to this method which will compose together any of the event handlers you need to apply to the input while preserving the ones that downshift needs to apply to make the input behave.

There are no required properties for this method.

Optional properties:

  • disabled: If this is set to true, then no event handlers will be returned from getInputProps and a disabled prop will be returned (effectively disabling the input).

  • ref: if you need to access the input element via a ref object, you'd call the function like this: getInputProps({ref: yourInputRef}). As a result, the input element will receive a composed ref property, which guarantees that both your code and useCombobox use the same correct reference to the element.

  • refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getInputProps({refKey: 'innerRef'}) and your composite component would forward like: <input ref={props.innerRef} />. However, if you are just rendering a primitive component like <div>, there is no need to specify this property. It defaults to ref.

In some cases, you might want to completely bypass the refKey check. Then you can provide the object {suppressRefError : true} as the second argument to getInput. Please use it with extreme care and only if you are absolutely sure that the ref is correctly forwarded otherwise useCombobox will unexpectedly fail.

This method should be applied to the input wrapper element. It has similar return values to the getRootProps from vanilla Downshift, but renaming it as it's not a root element anymore. We are encouraging the correct combobox HTML structure as having the combobox wrapper as a root for the rest of the elements broke navigation and readings with assistive technologies. The wrapper should contain the input and the toggleButton and it should be on the same level with the menu.

There are no required properties for this method.

In some cases, you might want to completely bypass the refKey check. Then you can provide the object {suppressRefError : true} as the second argument to getComboboxProps. Please use it with extreme care and only if you are absolutely sure that the ref is correctly forwarded otherwise useCombobox will unexpectedly fail.

useComboboxPrimitive actions
useComboboxPrimitive actions page anchor

These are functions you can call to change the state of the downshift useCombobox hook.

propertytypedescription
closeMenufunction()closes the menu
openMenufunction()opens the menu
selectItemfunction(item: any)selects the given item
setHighlightedIndexfunction(index: number)call to set a new highlighted index
setInputValuefunction(value: string)call to set a new value in the input
toggleMenufunction()toggle the menu open state
resetfunction()this resets downshift's state to a reasonable default
useComboboxPrimitive state
useComboboxPrimitive state page anchor

These are values that represent the current state of the downshift component.

propertytypedescription
highlightedIndexnumberthe currently highlighted item
isOpenbooleanthe menu open state
selectedItemanythe currently selected item input
inputValuestringthe value in the input

useMultiSelectPrimitive basic props

useMultiSelectPrimitive basic props page anchor

This is the list of props that you should probably know about. There are some advanced props below as well.

itemToString function(item: any) | defaults to: i => (i == null ? '' : String(i))

If your items are stored as, say, objects instead of strings, downshift still needs a string representation for each one. This is required for accessibility aria-live messages (e.g., after removing a selection).

onSelectedItemsChange function(changes: object) | optional, no useful default

Called each time the selected items array changes. Especially useful when items are removed, as there are many ways to do that: Backspace from dropdown, Backspace or Delete while focus is the item, executing removeSelectedItem when clicking an associated X icon for the item.

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the selectedItems property with the new array value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger an selectedItems change, for example useSelect.stateChangeTypes.DropdownKeyDownBackspace.
stateReducer function(state: object, actionAndChanges: object) | optional

This is a really handy power feature

This function will be called each time useMultipleSelection sets its internal state (or calls your onStateChange handler for control props). It allows you to modify the state change that will take place which can give you fine grain control over how the component interacts with user updates. It gives you the current state and the state that will be set, and you return the state that you want to set.

  • state: The full current state of downshift.
  • actionAndChanges: Object that contains the action type, props needed to return a new state based on that type and the changes suggested by the Downshift default reducer. About the type property you can learn more about in the stateChangeTypes section.

useMultiSelectPrimitive advanced props

useMultiSelectPrimitive advanced props page anchor
keyNavigationNext string | defaults to ArrowRight

The navigation key that increments activeIndex and moves focus to the selected item whose index corresponds to the new value. For a RTL scenario, a common overriden value could be ArrowLeft. In some scenarios it can be ArrowDown. It mostly depends on the UI the user is presented with.

keyNavigationPrevious string | defaults to ArrowLeft

The navigation key that decrements activeIndex and moves focus to the selected item whose index corresponds to the new value. Also moves focus from dropdown to item with the last index. For a RTL scenario, a common overriden value could be ArrowRight. In some scenarios it can be ArrowUp. It mostly depends on the UI the user is presented with.

initialSelectedItems any[] | defaults to []

Pass an initial array of items that are considered to be selected.

initialActiveIndex number | defaults to -1

Pass a number that sets the index of the focused / active selected item when downshift is initialized.

defaultSelectedItems any[] | defaults to []

Pass an array of items that are going to be used when downshift is reset.

defaultActiveIndex number | defaults to -1

Pass a number that sets the index of the focused / active selected item when downshift is reset.

getA11yRemovalMessage function({}) | default messages provided in English

This function is similar to the getA11yStatusMessage or getA11ySelectionMessage from useSelect and useCombobox but it is generating an ARIA a11y message when an item is removed. It is passed as props to a status updating function nested within that allows you to create your own ARIA statuses. It is called when an item is removed and the size of selectedItems decreases.

A default getA11yRemovalMessage function is provided. When an item is removed, the message is a removal related one, narrating "itemToString(removedItem) has been removed".

The object you are passed to generate your status message for getA11yRemovalMessage has the following properties:

propertytypedescription
resultCountnumberThe count of selected items in the list.
itemToStringfunction(any)The itemToString function (see props) for getting the string value from one of the options
removedSelectedItemanyThe value of the currently removed item
activeSelectedItemanyThe value of the currently active item
activeIndexnumberThe index of the currently active item.
onActiveIndexChange function(changes: object) | optional, no useful default

Called each time the index of the active item changes. When an item becomes active, it receives focus, so it can receive keyboard events. To change activeIndex you can either click on the item or use navigation keys between the items and the dropdown.

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the activeIndex property with the new value. This also has a type property which you can learn more about in the stateChangeTypes section.
onStateChange function(changes: object) | optional, no useful default

This function is called anytime the internal state changes. This can be useful if you're using downshift as a "controlled" component, where you manage some or all of the state (e.g. selectedItems and activeIndex) and then pass it as props, rather than letting downshift control all its state itself.

  • changes: These are the properties that actually have changed since the last state change. This also has a type property which you can learn more about in the stateChangeTypes section.

Tip: This function will be called any time any state is changed. The best way to determine whether any particular state was changed, you can use changes.hasOwnProperty('propName') or use the on[statePropKey]Change props described above.

NOTE: This is only called when state actually changes. You should not attempt to use this to handle events. If you wish handle events, put your event handlers directly on the elements (make sure to use the prop getters though!

For example: <button onBlur={handleBlur} /> should be <button {...getDropdownProps({onBlur: handleBlur})} />).

activeIndex number | control prop

(read more about this in the Control Props section)

The index of the item that should be active and focused.

selectedItems any[] | control prop

(read more about this in the Control Props section)

The items that are considered selected at the time.

environment window | defaults to window

This prop is only useful if you're rendering downshift within a different window context from where your JavaScript is running; for example, an iframe or a shadow-root. If the given context is lacking document and/or add|removeEventListener on its prototype (as is the case for a shadow-root) then you will need to pass in a custom object that is able to provide access to these properties(link takes you to an external page) for downshift.

useMultiSelectPrimitive state change types

useMultiSelectPrimitive state change types page anchor

There are a few props that expose changes to state (onStateChange and stateReducer). For you to make the most of these APIs, it's important for you to understand why state is being changed. To accomplish this, there's a type property on the changes object you get. This type corresponds to a stateChangeTypes property.

The list of all possible values this type property can take is defined in [this file][state-change-file] and is as follows:

  • useMultipleSelection.stateChangeTypes.SelectedItemClick
  • useMultipleSelection.stateChangeTypes.SelectedItemKeyDownDelete
  • useMultipleSelection.stateChangeTypes.SelectedItemKeyDownBackspace
  • useMultipleSelection.stateChangeTypes.SelectedItemKeyDownNavigationNext
  • useMultipleSelection.stateChangeTypes.SelectedItemKeyDownNavigationPrevious
  • useMultipleSelection.stateChangeTypes.DropdownKeyDownNavigationPrevious
  • useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace
  • useMultipleSelection.stateChangeTypes.DropdownClick
  • useMultipleSelection.stateChangeTypes.FunctionAddSelectedItem
  • useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem
  • useMultipleSelection.stateChangeTypes.FunctionSetSelectedItems
  • useMultipleSelection.stateChangeTypes.FunctionSetActiveIndex
  • useMultipleSelection.stateChangeTypes.FunctionReset

useMultiSelectPrimitive control Props

useMultiSelectPrimitive control Props page anchor

Downshift manages its own state internally and calls your onSelectedItemsChange, onActiveIndexChange and onStateChange handlers with any relevant changes. The state that downshift manages includes: selectedItems and activeIndex. Returned action function (read more below) can be used to manipulate this state and can likely support many of your use cases.

However, if more control is needed, you can pass any of these pieces of state as a prop (as indicated above) and that state becomes controlled. As soon as this.props[statePropKey] !== undefined, internally, downshift will determine its state based on your prop's value rather than its own internal state. You will be required to keep the state up to date (this is where onStateChange comes in really handy), but you can also control the state from anywhere, be that state from other components, redux, react-router, or anywhere else.

Note: This is very similar to how normal controlled components work elsewhere in react (like <input />).

useMultipleSelection returned props

useMultipleSelection returned props page anchor

The properties of useMultipleSelection can be split into three categories as indicated below.

useMultiSelectPrimitive prop getters
useMultiSelectPrimitive prop getters page anchor
(information)
On prop getters and accessibility

These prop-getters provide `aria-` attributes which are very important > to your component being accessible. It's recommended that you utilize these > functions and apply the props they give you to your components.

These functions are used to apply props to the elements that you render. This gives you maximum flexibility to render what, when, and wherever you like. You call these on the element in question, for example on the toggle button:

<button {...getDropdownProps()}

It's advisable to pass all your props to that function rather than applying them on the element yourself to avoid your props being overridden (or overriding the props returned).

For example: getDropdownProps({preventKeyAction: isOpen}).

See this blog post about prop getters(link takes you to an external page)

propertytypedescription
getDropdownPropsfunction({})returns the props you should apply to either your input or toggle button, depending on the case.
getSelectedItemPropsfunction({})returns the props you should apply to any selected item elements you render.

The props returned from calling this function should be applied to any selected items you render. It allows changing the activeIndex by using arrow keys or by clicking, but also removing items by Delete or Backspace on active item. It also ensures that focus moves along with the activeIndex, and it keeps a tabindex="0" on the active element even if user decides to Tab away. That way, when tabbing back, the user can pick up where he left off with selection.

This is an impure function, so it should only be called when you will actually be applying the props to an item.

Required properties:

It is required to pass either selectedItem or index to getSelectedItemProps in order to be able to apply the activeIndex logic.

  • selectedItem: this is the item data that will be selected when the user selects a particular item.
  • index: This is how downshift keeps track of your item when updating the activeIndex as the user keys around. By default, downshift will assume the index is the order in which you're calling getSelectedItemProps. This is often good enough, but if you find odd behavior, try setting this explicitly. It's probably best to be explicit about index when using a windowing library like react-virtualized.

Optional properties:

  • ref: if you need to access the dropdown element via a ref object, you'd call the function like this: getDropdown({ref: yourDropdownRef}). As a result, the dropdown element will receive a composed ref property, which guarantees that both your code and useMultipleSelection use the same correct reference to the element.

  • refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getSelectedItemProps({refKey: 'innerRef'}) and your composite component would forward like: <li ref={props.innerRef} />. However, if you are just rendering a primitive component like <div>, there is no need to specify this property. It defaults to ref.

Call this and apply the returned props to a button if you are building a select or to an input if you're building a combobox. It allows you to move focus from this element to the last item selected by using ArrowLeft and also to remove the last item using Backspace.

Optional properties:

  • preventKeyAction: tells useMultipleSelection if dropdown is allowed to execute downshift handlers on keydown. For example, you can pass isOpen as value and user will not be able to delete selecteditems by Backspace or to navigate to them by arrow keys. This is useful if you don't want to mix key actions from multiple selection with the ones from the dropdown. Once the dropdown is closed then deletion / navigation can be resumed for multiple selection. The value is false by default.
  • refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getDropdownProps({refKey: 'innerRef'}) and your composite component would forward like: <button ref={props.innerRef} />. However, if you are just rendering a primitive component like <div>, there is no need to specify this property. It defaults to ref.

In some cases, you might want to completely bypass the refKey check. Then you can provide the object {suppressRefError: true} as the second argument to getDropdownProps.

Please use it with extreme care and only if you are absolutely sure that the ref is correctly forwarded otherwise useMultipleSelection will unexpectedly fail.

const {getDropdownProps} = useMultipleSelection()
const {isOpen, ...rest} = useComboboxPrimitve({items})
const myButton = (
  {/* selected items */}
  <button {...getDropdownProps({preventKeyAction: isOpen})}>Click me</button>
  {/* menu and items */}
)

These are functions you can call to change the state of the downshift useMultipleSelection hook.

propertytypedescription
addSelectedItemfunction(item: any)adds an item to the selected array
removeSelectedItemfunction(item: any)removes an item from the selected array
resetfunction()resets the selectedItems and active index to defaults
setActiveIndexfunction(index: number)sets activeIndex to the new value
setSelectedItemsfunction(items: any[])sets selectedItems to the new value

These are values that represent the current state of the downshift component.

propertytypedescription
activeIndexnumberthe index of thecurrently active item
selectedItemsany[]the items of the selection

Downshift has a few events for which it provides implicit handlers. Several of these handlers call event.preventDefault(). Their additional functionality is described below.

Dropdown - button or input page anchor
  • ArrowLeft: Moves focus from button/input to the last selected item and makes activeIndex to be selectedItems.length - 1. Performs this action if there are any items selected. ArrowLeft can be overriden with any other key depeding on the requirements.
  • Backspace: Removes the last selected item from selection. It always performs this action on a non-input element. If the dropdown is a combobox the text cursor of the input must be at the start of the input and not highlight any text in order for the removal to work.
  • Click: It will make the item active, will modify activeIndex to reflect the new change, and will add focus to that item.
  • Delete: It will remove the item from selection. activeIndex will stay the same if the item removed was not the last one, but focus will move to the item which now has that index. If the last item was removed, the activeIndex will decrease by one and will also move focus to the corresponding item. If there are no items available anymore, the focus moves to the dropdown and activeIndex becomes -1.
  • Backspace: Same effect as Delete.
  • ArrowLeft: Moves activeIndex and focus to previous item. It stops at the first item in the selection. ArrowLeft can be overriden with any other key depeding on the requirements.
  • ArrowRight: Moves activeIndex and focus to next item. It will move focus to the dropdown if it occurs on the last selected item. ArrowRight can be overriden with any other key depeding on the requirements.

You can provide your own event handlers to useMultipleSelection which will be called before the default handlers:

const items = [...] // items here.
const {getDropdownProps} = useMultipleSelection()
const {getInputProps} = useCombobox({items})
const ui = (
  /* label, selected items, ... */
  <input
    {...getInputProps(
      getDropdownProps({
        onKeyDown: event => {
          // your custom keyDown handler here.
        },
      }),
    )}
  />
)

If you would like to prevent the default handler behavior in some cases, you can set the event's preventDownshiftDefault property to true:

const items = [...] // items here.
const {getDropdownProps} = useMultipleSelection()
const {getInputProps} = useCombobox({items})
const ui = (
  /* label, selected items, ... */
  <input
    {...getInputProps(
      getDropdownProps({
        onKeyDown: event => {
          // your custom keyDown handler here.
          if (event.key === 'Enter') {
            // Prevent Downshift's default 'Enter' behavior.
            event.nativeEvent.preventDownshiftDefault = true

            // your handler code
          }
        },
      }),
    )}
  />
)

If you would like to completely override Downshift's behavior for a handler, in favor of your own, you can bypass prop getters:

const items = [...] // items here.
const {getDropdownProps} = useMultipleSelection()
const {getInputProps} = useCombobox({items})
const ui = (
  /* label, selected items, ... */
  <input
    {...getInputProps(
      getDropdownProps({
        onKeyDown: event => {
          // your custom keyDown handler here.
        },
      }),
    )}
    onKeyDown={event => {
      // your custom keyDown handler here.
    }}
  />
)