You have a combobox or 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.
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, list and list items. The props are similar to the
ones 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 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, highlight by character keys etc.
By default, our implementation and examples illustrate an autocomplete of type list. This involves performing your own items filtering logic as well as keeping the aria_autocomplete value returned by the getInputProps.
There are, in total, 3 types of autocomplete you can opt for, and these are as follows:
- no autocomplete:
- ARIA example
- use aria-autocomplete="none" attribute to override the default value from getInputProps.
- do not implement any filtering logic yourself, just render the listbox items. Basically, take the code example below, remove the useState with items, the onInputValueChange function, pass colors as items prop and render the colors if isOpen is true.
- list autocomplete:
- ARIA example
- just use the example provided below or anything equivalent.
- filtering logic inside the menu is done by the useCombobox consumer.
- list and inline autocomplete:
- ARIA example
- use aria-autocomplete="both" attribute to override the default value from getInputProps.
- filtering logic inside the menu is done by the useCombobox consumer.
- inline autocomplete based on the highlighted item in the menu is also performed by the consumer.
The hook received breaking changes related to how it works, as well as the API, starting with v7. They are documented here:
- Usage
- Basic Props
- Advanced Props
- isItemDisabled
- initialSelectedItem
- initialIsOpen
- initialHighlightedIndex
- initialInputValue
- defaultSelectedItem
- defaultIsOpen
- defaultHighlightedIndex
- defaultInputValue
- itemToKey
- getA11yStatusMessage
- onHighlightedIndexChange
- onIsOpenChange
- onInputValueChange
- onStateChange
- highlightedIndex
- isOpen
- selectedItem
- inputValue
- id
- labelId
- menuId
- toggleButtonId
- inputId
- getItemId
- environment
- stateChangeTypes
- Control Props
- Returned props
- Event Handlers
- Examples
import * as React from 'react'
import {render} from 'react-dom'
import {useCombobox} from 'downshift'
const colors = [
'Black',
'Red',
'Green',
'Blue',
'Orange',
'Purple',
'Pink',
'Orchid',
'Aqua',
'Lime',
'Gray',
'Brown',
'Teal',
'Skyblue',
]
function DropdownCombobox() {
const [inputItems, setInputItems] = React.useState(colors)
const {
isOpen,
getToggleButtonProps,
getLabelProps,
getMenuProps,
getInputProps,
highlightedIndex,
getItemProps,
selectedItem,
selectItem,
} = useCombobox({
items: inputItems,
onInputValueChange: ({inputValue}) => {
setInputItems(
colors.filter(item =>
item.toLowerCase().startsWith(inputValue.toLowerCase()),
),
)
},
})
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
width: 'fit-content',
justifyContent: 'center',
marginTop: 100,
alignSelf: 'center',
}}
>
<label
style={{
fontWeight: 'bolder',
color: selectedItem ? selectedItem : 'black',
}}
{...getLabelProps()}
>
Choose an element:
</label>
<div>
<input
style={{padding: '4px'}}
{...getInputProps()}
data-testid="combobox-input"
/>
<button
style={{padding: '4px 8px'}}
aria-label="toggle menu"
data-testid="combobox-toggle-button"
{...getToggleButtonProps()}
>
{isOpen ? <>↑</> : <>↓</>}
</button>
<button
style={{padding: '4px 8px'}}
aria-label="toggle menu"
data-testid="clear-button"
onClick={() => selectItem(null)}
>
✗
</button>
</div>
<ul
{...getMenuProps()}
style={{
listStyle: 'none',
width: '100%',
padding: '0',
margin: '4px 0 0 0',
}}
>
{isOpen &&
inputItems.map((item, index) => (
<li
style={{
padding: '4px',
backgroundColor: highlightedIndex === index ? '#bde4ff' : null,
}}
key={`${item}${index}`}
{...getItemProps({
item,
index,
})}
>
{item}
</li>
))}
</ul>
</div>
)
}
render(<DropdownCombobox />, document.getElementById('root'))
This is the list of props that you should probably know about. There are some advanced props below as well.
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
.
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 aria-live 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>
.
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 theselectedItem
property with the newly selected value. This also has atype
property which you can learn more about in thestateChangeTypes
section. This property will be part of the actions that can trigger aselectedItem
change, for exampleuseCombobox.stateChangeTypes.ItemClick
.
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 actiontype
, props needed to return a new state based on that type and the changes suggested by the Downshift default reducer. About thetype
property you can learn more about in thestateChangeTypes
section.
import {useCombobox} from 'downshift'
import {items} from './utils'
const {getMenuProps, getItemProps, ...rest} = useCombobox({
items,
stateReducer,
})
function stateReducer(state, actionAndChanges) {
const {type, changes} = actionAndChanges
// this prevents the menu from being closed when the user selects an item with 'Enter' or mouse
switch (type) {
case useCombobox.stateChangeTypes.InputKeyDownEnter:
case useCombobox.stateChangeTypes.ItemClick:
return {
...changes, // default Downshift new state changes on item selection.
isOpen: state.isOpen, // but keep menu open.
highlightedIndex: state.highlightedIndex, // with the item highlighted.
}
default:
return changes // otherwise business as usual.
}
}
NOTE: This is only called when state actually changes. You should not attempt 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
<button onBlur={handleBlur} />
should be<button {...getToggleButtonProps({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.
function(item: any, index: number)
| defaults to:(_item, _index) => false
If an item needs to be marked as disabled, this function needs to return true
for that item. Disabled items will be skipped from keyboard navigation, will not
be selected and will be marked as disabled for screen readers.
any
| defaults tonull
Pass an item that should be selected when downshift is initialized.
boolean
| defaults tofalse
Pass a boolean that sets the open state of the menu when downshift is initialized.
number
| defaults to-1
Pass a number that sets the index of the highlighted item when downshift is initialized.
string
| defaults to''
Pass a string that sets the content of the input when downshift is initialized.
any
| defaults tonull
Pass an item that should be selected when downshift is reset.
boolean
| defaults tofalse
Pass a boolean that sets the open state of the menu when downshift is reset or when an item is selected.
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.
string
| defaults to''
Pass a string that sets the content of the input when downshift is reset or when an item is selected.
function(item: any)
| defaults to:item => item
Used to determine the uniqueness of an item when searching for the item or comparing the item with another. Returns the item itself, by default, so the comparing/searching is done internally via referential equality.
If using items as objects and their reference will change during use, you can
use the function to generate a unique key for each item, such as an id
prop.
function itemToKey(item) {
return item.id
}
This deprecates the "selectedItemChanged" prop. If you are using the prop already, make sure you change to "itemToKey" as the former is removed in v9. A migration example:
// initial items.
const items = [
{id: 1, value: 'Apples'},
{id: 2, value: 'Oranges'},
]
// the same items but with different references, for any reason.
const newItems = [
{id: 1, value: 'Apples'},
{id: 2, value: 'Oranges'},
]
// previously, if you probably had something like this.
function selectedItemChanged(item1, item2) {
return item1.id === item2.id
}
// moving forward, switch to this one.
function itemToKey(item) {
return item.id
// and we will do the comparison like: const isChanged = itemToKey(prevSelectedItem) !== itemToKey(nextSelectedItem)
}
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 the state
changes: selectedItem
, highlightedIndex
, inputValue
or isOpen
.
There is no default function provided anymore since v9, so if there's no prop passed, no aria live status message is created. An implementation that resembles the previous default is written below, should you want to keep pre v9 behaviour.
We don't provide this as a default anymore since we consider that screen readers have been significantly improved and they can convey information about items count, possible actions and highlighted items only from the HTML markup, without the need for aria-live regions.
function getA11yStatusMessage(state) {
if (!state.isOpen) {
return ''
}
// you need to get resultCount and previousResultCount yourself now, since we don't pass them as arguments anymore
const resultCount = items.length
const previousResultCount = previousResultCountRef.current
if (!resultCount) {
return 'No results are available.'
}
if (resultCount !== previousResultCount) {
return `${resultCount} result${
resultCount === 1 ? ' is' : 's are'
} available, use up and down arrow keys to navigate. Press Enter key to select.`
}
return ''
}
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. 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 thehighlightedIndex
property with the new value. This also has atype
property which you can learn more about in thestateChangeTypes
section. This property will be part of the actions that can trigger ahighlightedIndex
change, for exampleuseCombobox.stateChangeTypes.InputKeyDownArrowUp
.
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 theisOpen
property with the new value. This also has atype
property which you can learn more about in thestateChangeTypes
section. This property will be part of the actions that can trigger aisOpen
change, for exampleuseCombobox.stateChangeTypes.ToggleButtonClick
.
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 theinputValue
property with the new value. This also has atype
property which you can learn more about in thestateChangeTypes
section. This property will be part of the actions that can trigger ainputValue
change, for exampleuseCombobox.stateChangeTypes.InputChange
.
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 atype
property which you can learn more about in thestateChangeTypes
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 theon[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 {...getToggleButtonProps({onBlur: handleBlur})} />
).
number
| control prop (read more about this in the Control Props section)
The index of the item that should be highlighted when menu is open.
boolean
| control prop (read more about this in the Control Props section)
The open state of the menu.
any
| control prop (read more about this in the Control Props section)
The item that should be selected.
string
| control prop (read more about this in the Control Props section)
The value to be displayed in the text input.
🚨 Important 🚨
If you use onInputValueChange
, onStateChange
or anything similar in order to
update a state variable that will end up controlling inputValue
, you will
encounter a
cursor jump issue.
There's no way to properly fix this in our current React.useReducer
setup, so
in order to work around the issue, consider the change below.
const [value, setValue] = useState('')
const {getInputProps} = useCombobox({
items: [],
inputValue: value,
// change this:
onInputValueChange: ({inputValue}) => {
setValue(inputValue)
},
})
return (
<input
{...getInputProps({
// to this:
onChange: e => {
setValue(e.target.value)
},
})}
/>
)
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.
string
| defaults to a generated ID
Used for aria
attributes and the id
prop of the element (label
) you use
getLabelProps
with.
string
| defaults to a generated ID
Used for aria
attributes and the id
prop of the element (ul
) you use
getMenuProps
with.
string
| defaults to a generated ID
Used for aria
attributes and the id
prop of the element (button
) you use
getToggleButtonProps
with.
string
| defaults to a generated ID
Used for aria
attributes and the id
prop of the element (input
) you use
getInputProps
with.
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.
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.
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 and is as follows:
useCombobox.stateChangeTypes.InputKeyDownArrowDown
useCombobox.stateChangeTypes.InputKeyDownArrowUp
useCombobox.stateChangeTypes.InputKeyDownEscape
useCombobox.stateChangeTypes.InputKeyDownHome
useCombobox.stateChangeTypes.InputKeyDownEnd
useCombobox.stateChangeTypes.InputKeyDownPageUp
useCombobox.stateChangeTypes.InputKeyDownPadeDown
useCombobox.stateChangeTypes.InputKeyDownEnter
useCombobox.stateChangeTypes.InputChange
useCombobox.stateChangeTypes.InputClick
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
See stateReducer
for a concrete example on how to use the
type
property.
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 the Advanced React Component Patterns course
You use the hook like so:
import {useCombobox} from 'downshift'
import {items} from './utils'
const {getInputProps, reset, ...rest} = useCombobox({
items,
...otherProps,
})
return (
<div>
<input {...getInputProps()} />
{/* render the menu and items */}
{/* render a button that resets the select to defaults */}
<button
onClick={() => {
reset()
}}
>
Reset
</button>
</div>
)
NOTE: In this example we used both a getter prop
getInputProps
and an action propreset
. The properties ofuseCombobox
can be split into three categories as indicated below:
NOTE: 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 {...getToggleButtonProps()}
. 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:
getToggleButtonProps({onKeyDown(event) {console.log(event)}})
.
property | type | description |
---|---|---|
getToggleButtonProps |
function({}) |
returns the props you should apply to any menu toggle button element 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. |
getInputProps |
function({}) |
returns the props you should apply to the input element that you render. |
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 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 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 anaria-labelledby
that refers to the<label>
rendered withgetLabelProps
. However, if you providearia-label
to give a more specific label that describes the options available, thenaria-labelledby
will not be provided and screen readers can use youraria-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.
const {getMenuProps} = useCombobox({items})
const ui = (
<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
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.
What do you mean by impure function?
Basically just don't do this:
items.map((item, index) => {
const props = getItemProps({item, index}) // 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:
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 howdownshift
keeps track of your item when updating thehighlightedIndex
as the user keys around. By default,downshift
will assume theindex
is 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 aboutindex
when using a windowing library likereact-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 composedref
property, which guarantees that both your code anduseCombobox
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 thisinnerRef
. 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 toref
.
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 composedref
property, which guarantees that both your code anduseCombobox
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 thisinnerRef
. 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 toref
. -
disabled
: If this is set totrue
, then all of the downshift button event handlers will be omitted (it won't toggle the menu when clicked).
const {getToggleButtonProps} = useCombobox({items})
const myButton = (
<button {...getToggleButtonProps()}>Click me</button>
{/* menu and items */}
)
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 fromgetInputProps
and adisabled
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 composedref
property, which guarantees that both your code anduseCombobox
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 thisinnerRef
. 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 toref
. -
aria-label
: By default the input will add anaria-labelledby
that refers to the<label>
rendered withgetLabelProps
. However, if you providearia-label
to give a more specific label that describes the options available, thenaria-labelledby
will not be provided and screen readers can use youraria-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
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.
These are functions you can call to change the state of the downshift
useCombobox
hook.
property | type | description |
---|---|---|
closeMenu |
function() |
closes the menu |
openMenu |
function() |
opens the menu |
selectItem |
function(item: any) |
selects the given item |
setHighlightedIndex |
function(index: number) |
call to set a new highlighted index |
setInputValue |
function(value: string) |
call to set a new value in the input |
toggleMenu |
function() |
toggle the menu open state |
reset |
function() |
this resets downshift's state to a reasonable default |
These are values that represent the current state of the downshift component.
property | type | description |
---|---|---|
highlightedIndex |
number |
the currently highlighted item |
isOpen |
boolean |
the menu open state |
selectedItem |
any |
the currently selected item input |
inputValue |
string |
the value in the input |
Downshift has a few events for which it provides implicit handlers. Several of
these handlers call event.preventDefault()
. Their additional functionality is
described below.
Click
: If the menu is not displayed, it will open it. Otherwise it will close it. It will additionally move focus on the input in order for screen readers to correctly narrate which item is currently highlighted. If there is already an item selected, the menu will be opened with that item already highlighted.Enter
: Has the same effect asClick
. Button not in the tab order by default.Space
: Has the same effect asClick
. Button not in the tab order by default.
ArrowDown
: MoveshighlightedIndex
one position down. When reaching the last option,ArrowDown
will movehighlightedIndex
to first position.ArrowUp
: MoveshighlightedIndex
one position up. When reaching the first option,ArrowUp
will movehighlightedIndex
to last position.Alt+ArrowDown
: If the menu is closed, it will open it, without highlighting any item.Alt+ArrowUp
: If the menu is open, it will close it and will select the item that was highlighted.CharacterKey
: Will change theinputValue
according to the value visible in the<input>
.Backspace
orSpace
trigger the same event.End
: If the menu is open, it will highlight the last item in the list.Home
: If the menu is open, it will highlight the first item in the list.PageUp
: If the menu is open, it will move the highlight the item 10 positions before the current selection.PageDown
: If the menu is open, it will move the highlight the item 10 positions after the current selection.Enter
: If there is a highlighted option, it will select it and close the menu.Escape
: It will close the menu if open. If the menu is closed, it will clear selection: the value in theinput
will become an empty string and the item stored asselectedItem
will becomenull
.Click
: If the menu is closed, it will open it. If the menu is open, it will close it.Blur(Tab, Shift+Tab)
: It will close the menu and select the highlighted item, if any. The focus will move naturally to the next/previous element in the Tab order.Blur(mouse click outside)
: It will close the menu without selecting any element, even if there is one highlighted.
MouseLeave
: Will clear the value of thehighlightedIndex
if it was set.
Click
: It will select the item, close the menu and move focus to the toggle button (unlessdefaultIsOpen
is true).MouseOver
: It will highlight the item.
You can provide your own event handlers to useCombobox
which will be called
before the default handlers:
const items = [...] // items here.
const {getMenuProps} = useCombobox({items})
const ui = (
/* button, label, ... */
<ul
{...getMenuProps({
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 {getMenuProps} = useCombobox({items})
const ui = (
/* button, label, ... */
<ul
{...getMenuProps({
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 {getMenuProps} = useCombobox({items})
const ui = (
/* button, label, ... */
<ul
{...getMenuProps()}
onKeyDown={event => {
// your custom keyDown handler here.
}}
/>
)
Usage examples are kept on the downshift docsite and also on the sandbox repo. Each example has a link to its own Codesandbox version, so check the docs.
It can be a great contributing opportunity to provide relevant use cases as docsite examples. If you have such an example, please create an issue with the suggestion and the Codesandbox for it, and we will take it from there.