Skip to content

Latest commit

 

History

History

useSelect

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

useSelect

The problem

You have a custom select dropdown in your application and you want it to behave 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 dropdown select. You also want this solution to be simple to use and flexible so you can tailor it further to your specific needs.

This solution

useSelect is a React hook that manages all the stateful logic needed to make the dropdown 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. 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 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.

Migration through breaking changes

The hook received breaking changes related to how it works, as well as the API, starting with v7. They are documented here:

Table of Contents

Usage

Try it out in the browser

import * as React from 'react'
import {render} from 'react-dom'
import {useSelect} from 'downshift'

const colors = [
  'Black',
  'Red',
  'Green',
  'Blue',
  'Orange',
  'Purple',
  'Pink',
  'Orchid',
  'Aqua',
  'Lime',
  'Gray',
  'Brown',
  'Teal',
  'Skyblue',
]

function DropdownSelect() {
  const {
    isOpen,
    selectedItem,
    getToggleButtonProps,
    getLabelProps,
    getMenuProps,
    highlightedIndex,
    getItemProps,
  } = useSelect({items: colors})

  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
        style={{
          padding: '4px',
          textAlign: 'center',
          border: '1px solid black',
          backgroundColor: 'lightgray',
          cursor: 'pointer',
        }}
        {...getToggleButtonProps()}
      >
        {selectedItem ?? 'Elements'}
        {isOpen ? <>&#8593;</> : <>&#8595;</>}
      </div>
      <ul
        {...getMenuProps()}
        style={{
          listStyle: 'none',
          width: '100%',
          padding: '0',
          margin: '4px 0 0 0',
        }}
      >
        {isOpen &&
          colors.map((item, index) => (
            <li
              style={{
                padding: '4px',
                backgroundColor: highlightedIndex === index ? '#bde4ff' : null,
              }}
              key={`${item}${index}`}
              {...getItemProps({
                item,
                index,
              })}
            >
              {item}
            </li>
          ))}
      </ul>
    </div>
  )
}

render(<DropdownSelect />, document.getElementById('root'))

Basic Props

This is the list of props that you should probably know about. There are some advanced props below as well.

items

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.

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. This is required for accessibility aria-live messages (e.g., after making a selection) and for keyboard interaction features (e.g., highlighting an item by typing its first few characters).

onSelectedItemChange

function(changes: object) | optional, no useful default

Called each time the selected item was changed. Selection can be performed by item click, Enter or Space Key while item is highlighted or by using character keys while the toggle button is focused and menu is closed.

  • changes: These are the properties that actually have changed since the last state change. This object is guaranteed to contain the selectedItem property with the newly selected value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a selectedItem change, for example useSelect.stateChangeTypes.ItemClick.

stateReducer

function(state: object, actionAndChanges: object) | optional

🚨 This is a really handy power feature 🚨

This function will be called each time useSelect 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 action type, props needed to return a new state based on that type and the changes suggested by the Downshift default reducer. About the type property you can learn more about in the stateChangeTypes section.
import {useSelect} from 'downshift'
import {items} from './utils'

const {getMenuProps, getItemProps, ...rest} = useSelect({
  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 useSelect.stateChangeTypes.ToggleButtonKeyDownEnter:
    case useSelect.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.

Advanced Props

isItemDisabled

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.

initialSelectedItem

any | defaults to null

Pass an item that should be selected when downshift is initialized.

initialIsOpen

boolean | defaults to false

Pass a boolean that sets the open state of the menu when downshift is initialized.

initialHighlightedIndex

number | defaults to -1

Pass a number that sets the index of the highlighted item when downshift is initialized.

defaultSelectedItem

any | defaults to null

Pass an item that should be selected when downshift is reset.

defaultIsOpen

boolean | defaults to false

Pass a boolean that sets the open state of the menu when downshift is reset or when an item is selected.

defaultHighlightedIndex

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.

itemToKey

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.

// 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'},
]

function itemToKey(item) {
  return item.id
  // and we will do the comparison like: const isChanged = itemToKey(prevSelectedItem) !== itemToKey(nextSelectedItem)
}

getA11yStatusMessage

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 or Space Bar keys to select.`
  }

  return ''
}

onHighlightedIndexChange

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 the highlightedIndex property with the new value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a highlightedIndex change, for example useSelect.stateChangeTypes.ToggleButtonKeyDownArrowUp.

onIsOpenChange

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 the isOpen property with the new value. This also has a type property which you can learn more about in the stateChangeTypes section. This property will be part of the actions that can trigger a isOpen change, for example useSelect.stateChangeTypes.ToggleButtonClick.

onStateChange

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 a type property which you can learn more about in the stateChangeTypes 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 the on[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})} />).

highlightedIndex

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.

isOpen

boolean | control prop (read more about this in the Control Props section)

The open state of the menu.

selectedItem

any | control prop (read more about this in the Control Props section)

The item that should be selected.

id

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.

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.

toggleButtonId

string | defaults to a generated ID

Used for aria attributes and the id prop of the element (button) you use getToggleButtonProps 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 getItemProps with.

environment

window | defaults to window

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.

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 stateChangeTypes property.

The list of all possible values this type property can take is defined in this file and is as follows:

  • useSelect.stateChangeTypes.ToggleButtonKeyDownArrowDown
  • useSelect.stateChangeTypes.ToggleButtonKeyDownArrowUp
  • useSelect.stateChangeTypes.ToggleButtonKeyDownEscape
  • useSelect.stateChangeTypes.ToggleButtonKeyDownHome
  • useSelect.stateChangeTypes.ToggleButtonKeyDownEnd
  • useSelect.stateChangeTypes.ToggleButtonKeyDownPageUp
  • useSelect.stateChangeTypes.ToggleButtonKeyDownPageDown
  • useSelect.stateChangeTypes.ToggleButtonKeyDownEnter
  • useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton
  • useSelect.stateChangeTypes.ToggleButtonKeyDownCharacter
  • useSelect.stateChangeTypes.ToggleButtonBlur
  • useSelect.stateChangeTypes.ToggleButtonClick
  • useSelect.stateChangeTypes.MenuMouseLeave
  • useSelect.stateChangeTypes.ItemMouseMove
  • useSelect.stateChangeTypes.ItemClick
  • useSelect.stateChangeTypes.FunctionToggleMenu
  • useSelect.stateChangeTypes.FunctionOpenMenu
  • useSelect.stateChangeTypes.FunctionCloseMenu
  • useSelect.stateChangeTypes.FunctionSetHighlightedIndex
  • useSelect.stateChangeTypes.FunctionSelectItem
  • useSelect.stateChangeTypes.FunctionSetInputValue
  • useSelect.stateChangeTypes.FunctionReset

See stateReducer for a concrete example on how to use the type property.

Control Props

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

Returned props

You use the hook like so:

import {useSelect} from 'downshift'
import {items} from './utils'

const {getToggleButtonProps, reset, ...rest} = useSelect({
  items,
  ...otherProps,
})

return (
  <div>
    <div {...getToggleButtonProps()}>Options</div>
    {/* 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 getToggleButtonProps and an action prop reset. The properties of useSelect can be split into three categories as indicated below:

prop getters

See the blog post about prop getters

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 the trigger element you render. Since it will receive the role of combobox, we chose a <div> element.
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.

getLabelProps

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.

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:

  • ref: if you need to access the menu element via a ref object, you'd call the function like this: getMenuProps({ref: yourMenuRef}). As a result, the menu element will receive a composed ref property, which guarantees that both your code and useSelect 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 this innerRef. 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 to ref.

    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 an aria-labelledby that refers to the <label> rendered with getLabelProps. However, if you provide aria-label to give a more specific label that describes the options available, then aria-labelledby will not be provided and screen readers can use your aria-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 useSelect will unexpectedly fail.

const {getMenuProps} = useSelect({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.

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, 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 how downshift keeps track of your item when updating the highlightedIndex as the user keys around. By default, downshift will assume the index is the order in which you're calling getItemProps. This is often good enough, but if you find odd behavior, try setting this explicitly. It's probably best to be explicit about index when using a windowing library like react-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 composed ref property, which guarantees that both your code and useSelect 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 this innerRef. 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 to ref.

getToggleButtonProps

Call this and apply the returned props to a div element that will act as a triggering button. Since ARIA 1.2, the trigger element will reiceve a combobox role, so a <button> is not considered correct markup anymore. It allows you to toggle the Menu component.

Optional properties:

  • disabled: If this is set to true, then all of the downshift button event handlers will be omitted (it won't toggle the menu when clicked).

  • ref: if you need to access the button element via a ref object, you'd call the function like this: getToggleButtonProps({ref: yourToggleButtonRef}). As a result, the button element will receive a composed ref property, which guarantees that both your code and useSelect 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 this innerRef. 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 to ref.

  • aria-label: By default the toggle element will add an aria-labelledby that refers to the <label> rendered with getLabelProps. However, if you provide aria-label to give a more specific label that describes the options available, then aria-labelledby will not be provided and screen readers can use your aria-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 getToggleButtonProps. Please use it with extreme care and only if you are absolutely sure that the ref is correctly forwarded otherwise useSelect will unexpectedly fail.

const {getToggleButtonProps} = useSelect({items})
const myButton = (
  <button {...getToggleButtonProps()}>Click me</button>
  {/* menu and items */}
)

actions

These are functions you can call to change the state of the downshift useSelect 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
toggleMenu function() toggle the menu open state
reset function() this resets downshift's state to a reasonable default

state

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
keysSoFar string the character keys typed so far

Event Handlers

Downshift has a few events for which it provides implicit handlers. Several of these handlers call event.preventDefault(). Their additional functionality is described below.

Default handlers

Toggle Button

  • Click: If the menu is not displayed, it will open it. Otherwise it will close it. If there is already an item selected, the menu will be opened with that item already highlighted.
  • Enter: If the menu is closed, opens the menu. If the menu is opened and there is an item highlighted, it will select that item.
  • Space: If the menu is closed, opens the menu. If the menu is opened and there is an item highlighted, it will select that item. If the user has typed character keys before pressing Space, the space character will concatenate to the search query. This allows search for options such as Republic of ...
  • CharacterKey: Opens the menu if closed and highlights the first option that starts with that key. For instance, typing C will select the option that starts with C. Pressing C again will move the highlight to the next item that starts with C. Typing keys into rapid succession (in less than 500ms each) will select the option starting with that key combination, for instance typing CAL will select californium if this option exists.
  • ArrowDown: If the menu is closed, it will open it. If there is already an item selected, it will open the menu with the selected item highlighted. Otherwise, it will open the menu with the first option highlighted. If the menu is already open, it will highlight the next item.
  • ArrowUp: If the menu is closed, it will open it. If there is already an item selected, it will open the menu with the selected item highlighted. Otherwise, it will open the menu with the last option highlighted. If the menu is already open, it will highlight the previous item.
  • 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.
  • End: If the menu is closed, it will open it. It will also highlight the last item in the list.
  • Home: If the menu is closed, it will open it. It will also 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.
  • Escape: It will close the menu without selecting anything and keeps focus to the toggle button.
  • Blur(Tab, Shift+Tab, MouseClick outside): It will close the menu will select the highlighted item if any. Focus is handled naturally (next / previous elemenent in the tab order, body element if click outside.).

Menu

  • MouseLeave: Will clear the value of the highlightedIndex if it was set.

Item

  • Click: It will select the item and close the menu.
  • MouseOver: It will highlight the item.

Label

  • Click: It will move focus to the toggle element.

Customizing Handlers

You can provide your own event handlers to useSelect which will be called before the default handlers:

const items = [...] // items here.
const {getMenuProps} = useSelect({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} = useSelect({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} = useSelect({items})
const ui = (
  /* button, label, ... */
  <ul
    {...getMenuProps()}
    onKeyDown={event => {
      // your custom keyDown handler here.
    }}
  />
)

Examples

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.