Primitives to build simple, flexible, WAI-ARIA compliant React autocomplete, combobox or select dropdown components.
Read the docs | See the intro blog post | Listen to the Episode 79 of the Full Stack Radio podcast
The problem
You need an autocomplete, a combobox or a select experience in your application and you want it to be accessible. You also want it to be simple and flexible to account for your use cases. Finally, it should follow the ARIA design pattern for a combobox or a select, depending on your use case.
This solution
The library offers a couple of solutions. The first solution, which is the one we recommend you to try first, is a set of React hooks. Each hook provides the stateful logic needed to make the corresponding component functional and accessible. Navigate to the documentation for each by using the links in the list below.
- useSelect for a custom select component.
- useCombobox for a combobox or autocomplete input.
- useTagGroup for a tag group component. Also useful to build a multiple selection combobox or select component with tags.
The second solution is the Downshift component, which can also be used to
create accessible combobox and select components, providing the logic in the
form of a render prop. It served as inspiration for developing the hooks and it
has been around for a while. It established a successful pattern for making
components accessible and functional while giving developers complete freedom
when building the UI.
Both useSelect and useCombobox support the latest ARIA combobox patterns for W3C, which Downshift does not. Consequently, we strongly recommend the you use the hooks. The hooks have been migrated to the ARIA 1.2 combobox pattern in the version 7 of downshift. There is a Migration Guide that documents the changes introduced in version 7.
The README on this page covers only the component while each hook has its own
README page. You can navigate to the hooks page or go directly
to the hook you need by using the links in the list above.
For examples on how to use the hooks or the Downshift component, check out our docsite!
🚨 Use the Downshift hooks 🚨
If you are new to the library, consider the useSelect and useCombobox hooks as the first option. As mentioned above, the hooks benefit from the updated ARIA patterns and are actively maintained and improved. If there are use cases that are supported by the Downshift component and not by the hooks, please create an issue in our repo. The Downshift component is going to be removed completely once the hooks become mature.
Downshift
This is a component that controls user interactions and state for you so you can create autocomplete, combobox or select dropdown components. It uses a render prop which gives you maximum flexibility with a minimal API because you are responsible for the rendering of everything and you simply apply props to what you're rendering.
This differs from other solutions which render things for their use case and then expose many options to allow for extensibility resulting in a bigger API that is less flexible as well as making the implementation more complicated and harder to contribute to.
NOTE: The original use case of this component is autocomplete, however the API is powerful and flexible enough to build things like dropdowns as well.
Table of Contents
- Installation
- Usage
- Basic Props
- Advanced Props
- initialSelectedItem
- initialInputValue
- initialHighlightedIndex
- initialIsOpen
- defaultHighlightedIndex
- defaultIsOpen
- selectedItemChanged
- getA11yStatusMessage
- onSelect
- onStateChange
- onInputValueChange
- itemCount
- highlightedIndex
- inputValue
- isOpen
- selectedItem
- id
- inputId
- labelId
- menuId
- getItemId
- environment
- onOuterClick
- scrollIntoView
- stateChangeTypes
- Control Props
- Children Function
- Event Handlers
- Utilities
- React Native
- Advanced React Component Patterns course
- Examples
- FAQ
- Inspiration
- Other Solutions
- Bindings for ReasonML
- Contributors
- LICENSE
Installation
This module is distributed via npm which is bundled with node and
should be installed as one of your project's dependencies:
npm install --save downshift
This package also depends on
react. Please make sure you have it installed as well.
Note also this library supports
preactout of the box. If you are usingpreactthen use the corresponding module in thepreact/distfolder. You can evenimport Downshift from 'downshift/preact'👍
Usage
import * as React from 'react' import {render} from 'react-dom' import Downshift from 'downshift' const items = [ {value: 'apple'}, {value: 'pear'}, {value: 'orange'}, {value: 'grape'}, {value: 'banana'}, ] render( <Downshift onChange={selection => alert(selection ? `You selected ${selection.value}` : 'Selection Cleared') } itemToString={item => (item ? item.value : '')} > {({ getInputProps, getItemProps, getLabelProps, getMenuProps, isOpen, inputValue, highlightedIndex, selectedItem, getRootProps, }) => ( <div> <label {...getLabelProps()}>Enter a fruit</label> <div style={{display: 'inline-block'}} {...getRootProps({}, {suppressRefError: true})} > <input {...getInputProps()} /> </div> <ul {...getMenuProps()}> {isOpen ? items .filter(item => !inputValue || item.value.includes(inputValue)) .map((item, index) => ( <li {...getItemProps({ key: item.value, index, item, style: { backgroundColor: highlightedIndex === index ? 'lightgray' : 'white', fontWeight: selectedItem === item ? 'bold' : 'normal', }, })} > {item.value} </li> )) : null} </ul> </div> )} </Downshift>, document.getElementById('root'), )
There is also an example without getRootProps.
Warning: The example without
getRootPropsis not fully accessible with screen readers as it's not possible to achieve the HTML structure suggested by ARIA. We recommend following the example withgetRootProps. Examples on how to useDownshiftcomponent with and withoutgetRootPropsare on the docsite.
Downshift is the only component exposed by this package. It doesn't render
anything itself, it just calls the render function and renders that. "Use a
render prop!"!
<Downshift>{downshift => <div>/* your JSX here! */</div>}</Downshift>.
Basic Props
This is the list of props that you should probably know about. There are some advanced props below as well.
children
function({})| required
This is called with an object. Read more about the properties of this object in the section "Children Function".
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 (e.g., to set inputValue).
Note: This callback must include a null check: it is invoked with null
whenever the user abandons input via <Esc>.
onChange
function(selectedItem: any, stateAndHelpers: object)| optional, no useful default
Called when the selected item changes, either by the user selecting an item or
the user clearing the selection. Called with the item that was selected or
null and the new state of downshift. (see onStateChange for more info on
stateAndHelpers).
selectedItem: The item that was just selected.nullif the selection was cleared.stateAndHelpers: This is the same thing yourchildrenfunction is called with (see Children Function)
stateReducer
function(state: object, changes: object)| optional
🚨 This is a really handy power feature 🚨
This function will be called each time downshift 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 without having to use
Control Props. 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.changes: These are the properties that are about to change. This also has atypeproperty which you can learn more about in thestateChangeTypessection.
const ui = ( <Downshift stateReducer={stateReducer}>{/* your callback */}</Downshift> ) function stateReducer(state, changes) { // this prevents the menu from being closed when the user // selects an item with a keyboard or mouse switch (changes.type) { case Downshift.stateChangeTypes.keyDownEnter: case Downshift.stateChangeTypes.clickItem: return { ...changes, isOpen: state.isOpen, highlightedIndex: state.highlightedIndex, } default: return changes } }
NOTE: This is only called when state actually changes. You should not attempt to use this to handle events. If you wish to handle events, put your event handlers directly on the elements (make sure to use the prop getters though! For example:
<input onBlur={handleBlur} />should be<input {...getInputProps({onBlur: handleBlur})} />). Also, your reducer function should be "pure." This means it should do nothing other than return the state changes you want to have happen.
Advanced Props
initialSelectedItem
any| defaults tonull
Pass an item or an array of items that should be selected when downshift is initialized.
initialInputValue
string| defaults to''
This is the initial input value when downshift is initialized.
initialHighlightedIndex
number/null| defaults todefaultHighlightedIndex
This is the initial value to set the highlighted index to when downshift is initialized.
initialIsOpen
boolean| defaults todefaultIsOpen
This is the initial isOpen value when downshift is initialized.
defaultHighlightedIndex
number/null| defaults tonull
This is the value to set the highlightedIndex to anytime downshift is reset,
when the selection is cleared, when an item is selected or when the inputValue
is changed.
defaultIsOpen
boolean| defaults tofalse
This is the value to set the isOpen to anytime downshift is reset, when the
the selection is cleared, or when an item is selected.
selectedItemChanged
function(prevItem: any, item: any)| defaults to:(prevItem, item) => (prevItem !== item)
Used to determine if the new selectedItem has changed compared to the previous
selectedItem and properly update Downshift's internal state.
getA11yStatusMessage
function({/* see below */})| default messages provided in English
This function is passed as props to a Status component nested within and
allows you to create your own assertive ARIA statuses.
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."
The object you are passed to generate your status message has the following properties:
| property | type | description |
|---|---|---|
highlightedIndex |
number/null |
The currently highlighted index |
highlightedItem |
any |
The value of the highlighted item |
inputValue |
string |
The current input value |
isOpen |
boolean |
The isOpen state |
itemToString |
function(any) |
The itemToString function (see props) for getting the string value from one of the options |
previousResultCount |
number |
The total items showing in the dropdown the last time the status was updated |
resultCount |
number |
The total items showing in the dropdown |
selectedItem |
any |
The value of the currently selected item |
onSelect
function(selectedItem: any, stateAndHelpers: object)| optional, no useful default
Called when the user selects an item, regardless of the previous selected item.
Called with the item that was selected and the new state of downshift. (see
onStateChange for more info on stateAndHelpers).
selectedItem: The item that was just selectedstateAndHelpers: This is the same thing yourchildrenfunction is called with (see Children Function)
onStateChange
function(changes: object, stateAndHelpers: 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.
The parameters both take the shape of internal state
({highlightedIndex: number, inputValue: string, isOpen: boolean, selectedItem: any})
but differ slightly.
changes: These are the properties that actually have changed since the last state change. This also has atypeproperty which you can learn more about in thestateChangeTypessection.stateAndHelpers: This is the exact same thing yourchildrenfunction is called with (see Children Function)
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').
NOTE: This is only called when state actually changes. You should not attempt to use this to handle events. If you wish to handle events, put your event handlers directly on the elements (make sure to use the prop getters though! For example:
<input onBlur={handleBlur} />should be<input {...getInputProps({onBlur: handleBlur})} />).
onInputValueChange
function(inputValue: string, stateAndHelpers: object)| optional, no useful default
Called whenever the input value changes. Useful to use instead or in combination
of onStateChange when inputValue is a controlled prop to
avoid issues with cursor positions.
inputValue: The current value of the inputstateAndHelpers: This is the same thing yourchildrenfunction is called with (see Children Function)
itemCount
number| optional, defaults the number of times you call getItemProps
This is useful if you're using some kind of virtual listing component for
"windowing" (like
react-virtualized).
highlightedIndex
number| control prop (read more about this in the Control Props section)
The index that should be highlighted
inputValue
string| control prop (read more about this in the Control Props section)
The value the input should have
isOpen
boolean| control prop (read more about this in the Control Props section)
Whether the menu should be considered open or closed. Some aspects of the
downshift component respond differently based on this value (for example, if
isOpen is true when the user hits "Enter" on the input field, then the item at
the highlightedIndex item is selected).
selectedItem
any/Array(any)| control prop (read more about this in the Control Props section)
The currently selected item.
id
string| defaults to a generated ID
You should not normally need to set this prop. It's only useful if you're server
rendering items (which each have an id prop generated based on the downshift
id). For more information see the FAQ below.
inputId
string| defaults to a generated ID
Used for aria attributes and the id prop of the element (input) you use
getInputProps with.
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.
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
getInputProps with.
environment
window| defaults towindow
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
for downshift.
onOuterClick
function(stateAndHelpers: object)| optional
A helper callback to help control internal state of downshift like isOpen as
mentioned in this issue.
The same behavior can be achieved using onStateChange, but this prop is
provided as a helper because it's a fairly common use-case if you're controlling
the isOpen state:
const ui = ( <Downshift isOpen={this.state.menuIsOpen} onOuterClick={() => this.setState({menuIsOpen: false})} > {/* your callback */} </Downshift> )
This callback will only be called if isOpen is true.
scrollIntoView
function(node: HTMLElement, menuNode: HTMLElement)| defaults to internal implementation
This allows you to customize how the scrolling works when the highlighted index
changes. It receives the node to be scrolled to and the root node (the root node
you render in downshift). Internally we use
compute-scroll-into-view
so if you use that package then you wont be adding any additional bytes to your
bundle :)
stateChangeTypes
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 Downshift.stateChangeTypes
property.
The list of all possible values this type property can take is defined in
this file
and is as follows:
Downshift.stateChangeTypes.unknownDownshift.stateChangeTypes.mouseUpDownshift.stateChangeTypes.itemMouseEnterDownshift.stateChangeTypes.keyDownArrowUpDownshift.stateChangeTypes.keyDownArrowDownDownshift.stateChangeTypes.keyDownEscapeDownshift.stateChangeTypes.keyDownEnterDownshift.stateChangeTypes.keyDownHomeDownshift.stateChangeTypes.keyDownEndDownshift.stateChangeTypes.clickItemDownshift.stateChangeTypes.blurInputDownshift.stateChangeTypes.changeInputDownshift.stateChangeTypes.keyDownSpaceButtonDownshift.stateChangeTypes.clickButtonDownshift.stateChangeTypes.blurButtonDownshift.stateChangeTypes.controlledPropUpdatedSelectedItemDownshift.stateChangeTypes.touchEnd
See stateReducer for a concrete example on how to use the
type property.
Control Props
downshift manages its own state internally and calls your onChange and
onStateChange handlers with any relevant changes. The state that downshift
manages includes: isOpen, selectedItem, inputValue, and
highlightedIndex. Your Children 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
Children Function
This is where you render whatever you want to based on the state of downshift.
You use it like so:
const ui = ( <Downshift> {downshift => ( // use downshift utilities and state here, like downshift.isOpen, // downshift.getInputProps, etc. <div>{/* more jsx here */}</div> )} </Downshift> )
The properties of this downshift object can be split into three categories as
indicated below:
prop getters
NOTE: These prop-getters provide important
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:
<input {...getInputProps()})). 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:
getInputProps({onKeyUp(event) {console.log(event)}}).
| property | type | description |
|---|---|---|
getToggleButtonProps |
function({}) |
returns the props you should apply to any menu toggle button element you render. |
getInputProps |
function({}) |
returns the props you should apply to the input element that you render. |
getItemProps |
function({}) |
returns the props you should apply to any menu item elements you render. |
getLabelProps |
function({}) |
returns the props you should apply to the label element that you render. |
getMenuProps |
function({},{}) |
returns the props you should apply to the ul element (or root of your menu) that you render. |
getRootProps |
function({},{}) |
returns the props you should apply to the root element that you render. It can be optional. |
getRootProps
If you cannot render a div as the root element, then read this
Most of the time, you can just render a div yourself and Downshift will
apply the props it needs to do its job (and you don't need to call this
function). However, if you're rendering a composite component (custom component)
as the root element, then you'll need to call getRootProps and apply that to
your root element (downshift will throw an error otherwise).
There are no required properties for this method.
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 thisinnerRef. So you'd call:getRootProps({refKey: 'innerRef'})and your composite component would forward like:<div ref={props.innerRef} />. It defaults toref.
If you're rendering a composite component, Downshift checks that
getRootProps is called and that refKey is a prop of the returned composite
component. This is done to catch common causes of errors but, in some cases, the
check could fail even if the ref is correctly forwarded to the root DOM
component. In these cases, you can provide the object
{suppressRefError : true} as the second argument to getRootProps to
completely bypass the check.
Please use it with extreme care and only if you are absolutely sure that the
ref is correctly forwarded otherwise Downshift will unexpectedly fail.
See #235 for the
discussion that lead to this.
getInputProps
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 fromgetInputPropsand adisabledprop will be returned (effectively disabling the input). -
aria-label: By default the menu will add anaria-labelledbythat refers to the<label>rendered withgetLabelProps. However, if you providearia-labelto give a more specific label that describes the options available, thenaria-labelledbywill not be provided and screen readers can use youraria-labelinstead.
getLabelProps
This method should be applied to the label you render. It is useful for
ensuring that the for attribute on the <label> (htmlFor as a react prop)
is the same as the id that appears on the input. If no htmlFor is provided
(the normal case) then an ID will be generated and used for the input and the
label for attribute.
There are no required properties for this method.
Note: For accessibility purposes, calling this method is highly recommended.
getMenuProps
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 thisinnerRef. 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 toref.Please keep in mind that menus, for accessibility purposes, should always be rendered, regardless of whether you hide it or not. Otherwise,
getMenuPropsmay throw error if you unmount and remount the menu. -
aria-label: By default the menu will add anaria-labelledbythat refers to the<label>rendered withgetLabelProps. However, if you providearia-labelto give a more specific label that describes the options available, thenaria-labelledbywill not be provided and screen readers can use youraria-labelinstead.
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 Downshift will unexpectedly
fail.
<ul {...getMenuProps()}> {!isOpen ? null : items.map((item, index) => ( <li {...getItemProps({item, index, key: item.id})}>{item.name}</li> ))} </ul>
Note that for accessibility reasons it's best if you always render this element whether or not downshift is in an
isOpenstate.
getItemProps
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.
What do you mean by impure function?Basically just don't do this:
items.map(item => { const props = getItemProps({item}) // we're calling it here if (!shouldRenderItem(item)) { return null // but we're not using props, and downshift thinks we are... } return <div {...props} /> })
Instead, you could do this:
items.filter(shouldRenderItem).map(item => <div {...getItemProps({item})} />)
Required properties:
item: this is the item data that will be selected when the user selects a particular item.
Optional properties:
index: This is howdownshiftkeeps track of your item when updating thehighlightedIndexas the user keys around. By default,downshiftwill assume theindexis the order in which you're callinggetItemProps. This is often good enough, but if you find odd behavior, try setting this explicitly. It's probably best to be explicit aboutindexwhen using a windowing library likereact-virtualized.disabled: If this is set totrue, 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.
getToggleButtonProps
Call this and apply the returned props to a button. It allows you to toggle
the Menu component. You can definitely build something like this yourself (all
of the available APIs are exposed to you), but this is nice because it will also
apply all of the proper ARIA attributes.
Optional properties:
disabled: If this is set totrue, then all of the downshift button event handlers will be omitted (it wont toggle the menu when clicked).aria-label: Thearia-labelprop is in English. You should probably override this yourself so you can provide translations: