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.
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.
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:
|Adds an |
|Controls the open state of the list, adds ARIA attributes and event listeners.|
|Makes list focusable, adds ARIA attributes and event handlers.|
|Called with |
|Only when it's true we render the |
|Used to style the highlighted item.|
|Used 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.
<select> element can be created with HTML elements such as:
<button>. Using other HTML elements to create a
<select> is useful for the custom styling of the widget, since the
<select> is notoriously difficult to style.
<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.
the additional stateful logic to transform this selection of basic components
into a fully working dropdown element.
useSelect needs to perform some
scroll() logic on the DOM
elements, it requires refs to the
React components used. This example
illustrates how to use
MaterialUI, and shows how to correctly
pass refs to the hook.
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.
Controlling state is possible by receiving the state changes handled by
Downshift via onChange props (
etc.), changing data based on your requirements, and passing the data back to
Downshift via props, for instance
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.
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
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
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
ArrowDown should keep the
menu closed and change
selectedItem incrementally or decrementally. In the
stateReducer we capture the
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.
useSelect in an
<iframe> or in any other scenario that uses a
window object different than the default browser
window, it is required to
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
iframe container, we should pass its
window object to the hook
like shown below.
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
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.
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
selectItem action props in order to achieve these custom
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.
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
useVirtual, about which you can learn in
the react-virtual github link.