Skip to main content

useCombobox

The problem

You have a combobox/autocomplete dropdown in your application and you want it to be accessible and functional. For consistency reasons you want it to follow the ARIA design pattern for a combobox. You also want this solution to be simple to use and flexible so you can tailor it further to your specific needs.

This solution

useCombobox is a React hook that manages all the stateful logic needed to make the combobox functional and accessible. It returns a set of props that are meant to be called and their results destructured on the combobox's elements: its label, toggle button, input, combobox container, list and list items. The props are similar to the ones provided by the Downshift component to its children via render prop.

These props are called getter props and their return values are destructured as a set of ARIA attributes and event listeners. Together with the action props and state props, they create all the stateful logic needed for the combobox to implement the corresponding ARIA pattern. Every functionality needed should be provided out-of-the-box: menu toggle, item selection and up/down movement between them, screen reader support, focus management etc.

Breaking Changes in v8

useCombobox has been affected by breaking changes in v8, so check out the migration page.

Breaking Changes in v7

Since version 7, useCombobox to supports the ARIA 1.2 pattern for the combobox, which contains some changes from the ARIA 1.1 pattern. This brings changes in the API and the behaviour of useCombobox, detailed in the migration page.

Props used in examples

In the examples below, we use the useCombobox hook and destructure from its result the getter props and state variables. The hooks also has the onInputValueChange prop passed in order to filter the items in the list according to the input value. The getter props are used as follows:

Returned propElementComments
getLabelProps<label>Call and destructure its returned object on the label element.
getToggleButtonProps<button>Call and destructure its returned object on the toggle button (if any).
getInputProps<input>Call and destructure its returned object on the input element.
getMenuProps<ul>Call and destructure its returned object on the menu element.
getItemProps<li>Call with index or item and destructure its returned object on each menu item element.
isOpenState value with the open state of the menu. Used below for conditionally showing the items.
highlightedIndexState value with the index of thehighlighted menu item. Used below for styling.
selectedItemState value with the item that is selected. Used below for styling.
inputValueState value with the search query. Used below for filtering the items.

For a complete documentation on all the returned props, hook props and more information check out the Github Page.

Basic Usage

A combobox element can be created with HTML elements such as: label, ul, li, button, input and a div or something similar to contain the input and the toggle button. It is absolutely important to follow the HTML structure below, as it will allow all screen readers to properly work with the widget. Most importantly, the input needs to be contained by the combobox div and the ul needs to be at the same level with the combobox div.

CodeSandbox for basic usage example.

Live Editor
Result
Loading...

React Native

The hook can be used with React Native as well. The HTML elements and styles are replaced with React Native equivalents, but the useCombobox usage is exactly the same as on React web.

MaterialUI

A custom combobox/autocomplete element can be created using UI Library components as well. Many libraries will provide basic elements such as buttons, texts/labels, inputs and lists, which can be styled according to each library guidelines. useCombobox is providing the additional stateful logic that will transform this selection of basic components into a fully working dropdown component.

As useCombobox needs to perform some focus() and scroll() logic on the DOM elements, it will require the refs to the React components used. The example below will illustrate how to use useCombobox with MaterialUI library components and how to correctly pass refs to the hook where needed.

Since MaterialUI components already accept a ref prop that will be filled with the resulting DOM element, we don't need to do anything specific rather than just spreading the getter props, apart from the case of the Input, which renders a wrapper element over the actual HTML input. In this case, since Input provides a prop for accessing the input element called inputRef, we will use the getter function like this: getInputProps({refKey: 'inputRef'}).

Another point worth mentioning is that in this case items are objects and not strings. As a result, the itemToString prop is passed to useCombobox. It will return the string equivalent of the item which will be used for displaying the item in the input once selected and for the a11y aria-live message that will occur on every item selection: ${itemToString(item)} has been selected. item.title is chosen to be the string equivalent of each item object, so our prop will be passed as itemToString: item => item ? item.title : ''. Since clearing the input by Escape key is also considered an element change, we will return an empty string in this case.

CodeSandbox for MaterialUI usage example.

Live Editor
Result
Loading...

Controlling state

Controlling state is possible by receiving the state changes done by Downshift via onChange props (onHighlightedIndexChange, onSelectedItemChange, onStateChange etc.). You can then change them based on your requirements and pass them back to useCombobox as props, such as for instance highlightedIndex or selectedItem.

The example below shows how to control selectedItem with the help of React.useState.

CodeSandbox for controlling state example.

Live Editor
Result
Loading...

State Reducer

For an even more granular control of the state changing process, you can add your own reducer on top of the default one. When stateReducer is called it will receive the previous state and the actionAndChanges object. actionAndChanges contains the change type, which explains why the state is being changed. It also contains the changes proposed by Downshift that should occur as a consequence of that change type. You are supposed to return the new state according to your needs.

In the example below, let's say we want to show input characters uppercased all the time. In stateReducer we wait for the useCombobox.stateChangeTypes.InputChange event, get the proposed inputValue from the default reducer, uppercase the value, and return the new value along with the rest of the changes. We will also uppercase the inputValue also when a selection is performed, since on item selection the inputValue is changed based on the string version of the selected item.

In all other state change types, we return Downshift default changes.

CodeSandbox for state reducer example.

Live Editor
Result
Loading...

Custom window

When using useCombobox in an iframe or in any other scenario that uses a window object different than the default browser window, it is required to provide that window object to the hook as well. Internally, we rely on the window for DOM related logic and working with the wrong object will make the hook behave unexpectedly. For example, when using react-frame-component to produce an iframe container, we should pass its window object to the hook like shown below.

CodeSandbox for custom window example.

Live Editor
Result
Loading...

Basic Multiple Selection

The useCombobox hook can be used to create a widget that supports multiple selection. In the example below, we mark each selected item with a checked checkbox inside the menu list. Every other aspect remains the same as with the single selection combobox. For a more interactive example of multiple selection, you can use our useMultipleSelection hook together with useCombobox, as shown in the multiple selection section.

In the example below, we control the selectedItem to always be null and keep our selected items in a state variable, selectedItems. We use onSelectedItemChange prop to retrieve the selectedItem from useCombobox, which is added to / removed from the selectedItems array. We also use stateReducer to keep the menu open on selection by Enter key or by click, and also to keep the highlightedIndex to be the most recent selected item.

In order to visually illustrate the selection, we render a checkbox before each of them and check only the ones that are selected.

CodeSandbox for basic multiple selection example.

Live Editor
Result
Loading...

Using action props

Action props are functions returned by useCombobox along with the state props and getter props. They are handy when you need to execute combobox state changes from event handlers, state change handlers or any other external location. In the example below we clear the selection by clicking on the custom selection clearing button. We use the selectItem action prop in order to achieve this custom behavior.

CodeSandbox for action props example.

Live Editor
Result
Loading...

Virtualizing items with react-virtual

When the number of items in the dropdown is too big, you may want to consider using a virtualization technique to avoid loss in performance due to unnecessary elements rendered in the DOM. react-virtual is a great library to provide items virtualization and it's the one we will show in the example below. There are other libraries as well, such as react-virtualized and react-virtual.

Since react-virtual has its own scrolling library, we will use it instead of the default one from Downshift. Apart from this it's business as usual in both the case of using useCombobox and useVirtual, about which you can learn in the react-virtual github link.

CodeSandbox for virtualized list example.

Live Editor
Result
Loading...

Other usage examples

To see more cool stuff you can build with useCombobox, explore the examples repository.