Downshift LogoDownshift
useComboboxuseMultipleSelectionuseSelectThe problemThis solutionProps used in examplesBasic UsageMaterialUIControlling stateState ReducerCustom windowBasic Multiple selectionUsing action propsVirtualizing items with react-virtualOther usage examples


The problem

You have a custom select dropdown in your application and you want it to perform exactly the same as the native HTML <select> in terms of accessibility and functionality. For consistency reasons, you want it to follow the ARIA design pattern for a select. You also want this solution to be simple to use and flexible so you can tailor it to your needs.

This solution

useSelect is a React hook that manages all the stateful logic needed to make the select functional and accessible. It returns a set of props that are meant to be called, and their results destructured on the dropdown's elements: its label, toggle button, list and list items. These are similar to the props provided by vanilla <Downshift> to the children 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 dropdown 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, highlight by character keys etc.

Props used in examples

In the examples below, we use the useSelect hook and destructure the getter props and state variables it returns. These are used in the following way:

Returned propElementComments
getLabelProps<label>Adds an id attribute to be used for menu and toggleButton.
getToggleButtonProps<button>Controls the open state of the list, adds ARIA attributes and event listeners.
getMenuProps<ul>Makes list focusable, adds ARIA attributes and event handlers.
getItemProps<li>Called with index and item, adds ARIA attributes and event listeners.
isOpenOnly when it's true we render the <li> elements.
highlightedIndexUsed to style the highlighted item.
selectedItemUsed to render text equivalent of selected item on the button.

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

Basic Usage

A custom <select> element can be created with HTML elements such as: <label>, <ul>, <li> and <button>. Using other HTML elements to create a custom <select> is useful for the custom styling of the widget, since the <select> is notoriously difficult to style.

CodeSandbox for basic usage example.


    A custom <select> element can be created using UI Library components as well. Many libraries provide basic elements such as buttons, texts/labels, and lists, which can be styled according to each library guidelines. useSelect provides the additional stateful logic to transform this selection of basic components into a fully working dropdown element.

    As useSelect needs to perform some focus() and scroll() logic on the DOM elements, it requires refs to the React components used. This example illustrates how to use useSelect with MaterialUI, and shows how to correctly pass refs to the hook.

    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 other than just spreading the getter props.

    Another point worth mentioning is that in this case items are objects and not strings. As a result, the itemToString prop is passed to useSelect. It will return the string equivalent of the item which will be used for selection/highlight by character keys and for the aria-live a11y selectionmessage that will occur on every item selection: $ItemString has been selected. item.primary is chosen to be the string equialent of each item object.

    CodeSandbox for MaterialUI usage example.

      Controlling state

      Controlling state is possible by receiving the state changes handled by Downshift via onChange props (onHighlightedIndexChange, onSelectedItemChange etc.), changing data based on your requirements, and passing the data back to Downshift via props, for instance highlightedIndex or selectedItem.

      The example below shows how to control selectedItem. Both select elements share the same selectedItem reference, and changing it in one of the dropdowns will update the value in the other one as well.

      CodeSandbox for controlling state example.

          State Reducer

          For even more granular state change control, you can add your own reducer on top of the default one. When the stateReducer is called, it will receive the previous state and the actionAndChanges object as parameters. actionAndChanges contains the change type, which explains why the state is being changed. It also contains the changes proposed by useSelect 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, we are implementing a Windows-specific behavior for the select. While menu is closed, using ArrowUp and ArrowDown should keep the menu closed and change selectedItem incrementally or decrementally. In the stateReducer we capture the ToggleButtonKeyDownArrowDown and ToggleButtonKeyDownArrowUp events, compute the next selectedItem based on index, and return it without any other changes.

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

          CodeSandbox for state reducer example.

            Custom window

            When using useSelect 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.

            Basic Multiple selection

            The useSelect 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 dropdown. For a more interactive example of multiple selection, you can use our useMultipleSelection hook together with useSelect, 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 useSelect, 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.

              Using action props

              Action props are functions returned by useSelect along with the state props and getter props. They are handy when you need to execute select state changes from event handlers, state change handlers or any other external location. In the example below we open the menu when the toggle button is hovered, and clear the selection by clicking on the custom selection clearing button. We use the openMenu and selectItem action props in order to achieve these custom behaviors.

              CodeSandbox for action props example.

                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 useSelect and useVirtual, about which you can learn in the react-virtual github link.

                CodeSandbox for virtualized list example.

                  Other usage examples

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