Sign in
Log inSign up
Keyboard accessibility for Select component

Keyboard accessibility for Select component

J.J. Ashwin kumar's photo
J.J. Ashwin kumar
·Jul 27, 2021·

3 min read

Users always want a user-friendly websites. The keyboard accessibility is one of the major feature to be added for the the input components. In this blog, we are going to check out how to add keyboard accessibility for the select component. friendly

In this blog, I am not going to build the Select component from the scratch. Please check out my Previous Blog. This blog will guides you how to create a custom select component from scratch.

Hope you had checked out my previous blog. Now Let's add the keyboard accessibility functionality to the select component. First, we need to grab the keycodes for the Enter key, up and down arrow keys, and the Escape key.

// keys.ts
const UP_ARROW = 38;
const DOWN_ARROW = 40;
const ENTER_KEY = 13;
const ESCAPE_KEY = 27;

export { UP_ARROW, DOWN_ARROW, ENTER_KEY, ESCAPE_KEY };

Consider a Select component which has three options. The second option is highlighted. When we hit Up Arrow key, the first option should be highlighted (Move backward). When we hit the Down Arrow key, the third option should be highlighted (Move forward). the Enter key is to select the option that is currently highlighted. The Escape key is to close the dropdown and remove focus from the component. These are the functionalities for each keys.

keyboard-access.png

We need to add a new state called focusIndex. This state is used to maintain the index of the option that is highlighted or focused.

const [focusIndex, setFocusIndex] = useState(0);

Let's define the keyDownHandler function to handle the different key events. The Up Arrow key will decrease the focusIndex value by 1. The down Arrow key will increase the focusIndex value by 1. The Enter key will update the selected option based on the focusIndex.

  const keyHandler = (e: React.KeyboardEvent) => {
    const childrenCount = childrenArray.length;

    switch (e.keyCode) {
      case UP_ARROW: {
        // If the focusIndex is 0(First child) ,Update the focusIndex as the last index of the children Array
        if (focusIndex === 0) setFocusIndex(childrenCount - 1);
        else setFocusIndex(focusIndex - 1);
        break;
      }
      case DOWN_ARROW: {
        // If the focusIndex is the last index of the children Array(Last child) ,Update the focusIndex as 0.
        if (focusIndex === childrenCount - 1) setFocusIndex(0);
        else setFocusIndex(focusIndex + 1);
        break;
      }
      case ESCAPE_KEY: {
        setShowDropdown(false);
        selectContainerRef.current?.blur();
        break;
      }
      case ENTER_KEY: {
        const focusedOptionValue = childrenArray[focusIndex]?.props?.value;
        setSelectedOption(focusedOptionValue);
        setShowDropdown(false);
        break;
      }
      default: {
        break;
      }
    }
  };

We had defined the handlers for each keys. Now we need to call this function whenever the key is pressed in the select component.

// Select .ts
  <SelectContext.Provider
      value={{
        selectedOption,
        changeSelectedOption: updateSelectedOption,
      }}
    >
      <div
        className="select-container"
        ref={selectContainerRef}
        tabIndex={1}
        onKeyDown={keyHandler}
      >
      ....
      .... 
      </div>
    </SelectContext.Provider>

The final step is to provide highlighted background colour to the Option which is focused based on the focusIndex. In order to achieve it, We need focusIndex in the Option component. We can compare the focusIndex and the index of the each Option component and highlights the option if both indices matches.

We need to add the focusIndex in the selectContext and also pass the focusIndex value from the outer container(Select).

const SelectContext = createContext<{
  selectedOption: string;
  focusIndex: number;
  changeSelectedOption: (option: string) => void;
}>({
  selectedOption: "",
  focusIndex: 0,
  changeSelectedOption: (option: string) => {}
});

Now the focusIndex is shared. Use the focusIndex in the Option component to highlight the option based on its index.

const Option : React.FC<{
  children: ReactNode | ReactNode[];
  value: string;
  internal__select_index: number;
}>= ({ children, value, internal__select_index: index }) => {
  const { changeSelectedOption, focusIndex } = useSelectContext();

  //If the focusIndex matches, focused class is added in addition to select-option className
  return (
    <li
      className={
        index === focusIndex ? "select-option focused" : "select-option"
      }
      onClick={() => changeSelectedOption(value)}
    >
      {children}
    </li>
  );
};

Cheers folks. We have added the keyboard accessibility for the select component. You can check out the complete code and demo at the codesandbox. happy coding.

cheers