Since version 7.0.0 Downshift follows the (ARIA 1.2 guideline for the combobox)[select-aria]. This brought a series of changes that are considered breaking, both to the API and the behaviour of downshift's useSelect and useCombobox hooks. The list of changes, as well as the migration itself, is detailed below.
Since ARIA 1.2, focus stays on the trigger element at all times. (Previously)deprecated-select-aria, it toggled between the trigger and the menu depending on the open state of the select element. If any of your custom implementation involved the focus on the menu element, please change it as the focus stays on the trigger even when the menu is open.
Similar to 1.1, useSelect communicates to the screen reader the currently highlighted item via the aria-activedescendant attribute. However, since now the focus is always on the trigger element, this attribute, along with others, have shifted as shown below:
- getToggleButtonProps additions:
- role=combobox
- aria-activedescendant=${highlightedItemId}
- aria-controls=${menuId}
- tabindex=0
- getMenuProps removals:
- aria-activedescendant=${highlightedItemId}
- getItemProps changes:
- aria-selected=${item === selectedItem} - now the item that is selected received aria-selected=true, the rest receive it as false. Previously, the highlighted item was marked with aria-selected=true.
Event changes occured because of the focus shift, as well as new accessibility pattern recommendantions.
-
getToggleButtonProps additions:
- ArrowDown+Alt: opens the menu without any item highlighted.
- ArrowUp+Alt: closes the menu and selects the highlighted item.
- End: highlights the last item and opens the menu if closed.
- Home: highlights the first item and opens the menu if closed.
- PageUp: if menu is open, moves highlight by 10 positions to the start.
- PageDown: if menu is open, moves highlight by 10 positions to the end.
- ${characterKey}: always opens the menu if closed, highlights the item starting with that key (same behaviour as before when the menu is opened).
- Enter: if menu is open, closes the menu and selects the highlighted item.
- SpaceBar: if menu is open, closes the menu and selects the highlighted item. If the space is part of a search query, it will be added to the search query instead.
- Escape: closes the menu if open, without selecting anything.
- Tab or any other Blur: closes the menu if open, selects highlighted item, focus moves naturally.
- ArrowUp: moves highlight one position up. Shift modifier is not supported anymore.
- ArrowDown: moves highlight one position down. Shift modifier is not supported anymore.
-
getToggleButtonProps changes:
- ArrowUp: if there is an item selected, opens the menu with that item highlighted, not with the -1 offset as it did in v6 (ARIA 1.1).
- ArrowDown: if there is an item selected, opens the menu with that item highlighted, not with the +1 offset as it did in v6 (ARIA 1.1).
-
getMenuProps removals:
- ArrowUp, ArrowDown, End, Home, Enter, Escape, SpaceBar, Tab.
As a consequence of the event changes, the stateChangeTypes received in the stateReducer and on${statePropery}Change received the following modifications:
- MenuKeyDownArrowDown -> ToggleButtonKeyDownArrowDown
- MenuKeyDownArrowUp -> ToggleButtonKeyDownArrowUp
- MenuKeyDownEscape -> ToggleButtonKeyDownEscape
- MenuKeyDownHome -> ToggleButtonKeyDownHome
- MenuKeyDownEnd -> ToggleButtonKeyDownEnd
- MenuKeyDownEnter -> ToggleButtonKeyDownEnter
- MenuKeyDownSpaceButton -> ToggleButtonKeyDownSpaceButton
- MenuKeyDownCharacter -> ToggleButtonKeyDownCharacter
- MenuBlur -> ToggleButtonBlur
- ToggleButtonKeyDownPageUp: new state change type.
- ToggleButtonKeyDownPageDown: new state change type.
Please change your reducer / onChange code accordingly. For instance:
function stateReducer(state, actionAndChanges) {
const {changes, type} = actionAndChanges
switch (type) {
case useSelect.stateChangeTypes.MenuKeyDownEnter:
case useSelect.stateChangeTypes.MenuKeyDownSpaceButton:
case useSelect.stateChangeTypes.ItemClick:
return {
...changes,
isOpen: true, // keep the menu open after selection.
}
default:
return changes
}
}
Becomes:
function stateReducer(state, actionAndChanges) {
const {changes, type} = actionAndChanges
switch (type) {
case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton:
case useSelect.stateChangeTypes.ItemClick:
return {
...changes,
isOpen: true, // keep the menu open after selection.
}
default:
return changes
}
}
Another thing to mention is that, since ARIA 1.2 does not recommend a
<button>
element for the toggle button anymore, Enter and SpaceBar do not perform click events out of the box, when the menu is closed. Consequently, we are triggering these events manually. For backwards compatibility to pre v7, when menu is closed and toggle element receives Enter or SpaceBar key event, it will trigger auseSelect.stateChangeTypes.ToggleButtonClick
type change.
The prop circularNavigation has been removed. Navigation inside the menu is standard and non-circular. If you wish to make it circular, use the stateReducer:
function stateReducer(state, actionAndChanges) {
const {changes, type} = actionAndChanges
switch (type) {
case useSelect.stateChangeTypes.ToggleButtonKeyDownArrowDown:
if (state.highlightedIndex === items.length - 1) {
return {...changes, highlightedIndex: state.highlightedIndex}
} else {
return changes
}
case useSelect.stateChangeTypes.ToggleButtonKeyDownArrowUp:
if (state.highlightedIndex === 0) {
return {...changes, highlightedIndex: state.highlightedIndex}
} else {
return changes
}
default:
return changes
}
}
The biggest change in ARIA 1.2 is that the input wrapper element does not receive the combobox role attributes anymore. Previouslydeprecated-combobox-aria, the role of combobox, as well as other HTML attributes, had to be added on the input parent element. Some attributes that belonged to the wrapper element are now added on the input element. The changes are as follows:
- getComboboxProps has been removed.
- getInputProps additions:
- role=combobox
- aria-expanded=${isOpen}
- getToggleButtonProps additions:
- aria-controls=${menuId}
- aria-expanded=${isOpen}
As a result of the 1.2 pattern, there are a few event handling changes detailed below.
-
getInputProps additions:
- ArrowDown+Alt: opens the menu without any item highlighted.
- ArrowUp+Alt: closes the menu and selects the highlighted item.
- PageUp: if menu is open, moves highlight by 10 positions to the start.
- PageDown: if menu is open, moves highlight by 10 positions to the end.
- Focus: if menu is closed, opens the menu.
-
getInputProps changes:
- ArrowUp: moves highlight one position up. Shift modifier is not supported anymore.
- ArrowDown: moves highlight one position down. Shift modifier is not supported anymore.
As a consequence of the event changes, the stateChangeTypes received in the stateReducer and on${statePropery}Change received the following additions:
- InputKeyDownPageUp
- InputKeyDownPageDown
- InputFocus
You don't need to change your reducer if you want to keep the 1.2 functionality provided by default. However, if you want to keep the menu closed when the input gets focus, you can do:
function stateReducer(state, actionAndChanges) {
const {changes, type} = actionAndChanges
switch (type) {
case useCombobox.stateChangeTypes.InputFocus:
return {
...changes,
isOpen: state.isOpen, // keep the menu closed when input gets focused.
}
default:
return changes
}
}
The prop circularNavigation has been removed. Navigation inside the menu is standard and circular. If you wish to make it non-circular, use the stateReducer:
function stateReducer(state, actionAndChanges) {
const {changes, type} = actionAndChanges
switch (type) {
case useCombobox.stateChangeTypes.InputKeyDownArrowDown:
if (state.highlightedIndex === items.length - 1) {
return {...changes, highlightedIndex: state.highlightedIndex}
}
break
case useCombobox.stateChangeTypes.InputKeyDownArrowUp:
if (state.highlightedIndex === 0) {
return {...changes, highlightedIndex: state.highlightedIndex}
}
break
default:
return changes
}
return changes
}