Skip to content

[Accessibility] Unable to select an option using Windows Narrator #6080

@araldicami

Description

@araldicami

Hey,

I tested react-select using Windows Narrator, and I was unable to select any options. I open the select, and when I try to use the Arrow Down key to move to another option, the select closes.

I noticed that the onKeyDown function does not receive the Arrow Down event in the dropdown when Narrator is on.

I created a hook to handle this, but I’d appreciate it if react-select supported this natively.

Here you can check the bug:

react-select.w.narrator.mp4

The hook:

import {useCallback, useEffect, useRef, useState} from 'react';

interface KeyboardNavigation {
  initialValue?: any;
  onValueChange?: any;
}

export const useSelectKeyboardNavigation = ({
  initialValue,
  onValueChange,
}: KeyboardNavigation) => {
  const [value, setValue] = useState<any>(initialValue);
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  const selectRef = useRef<any>(null);
  const preventCloseRef = useRef(false);

  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (selectRef.current?.controlRef) {
        if (!selectRef.current.controlRef.contains(event.target as Node)) {
          const menu = document.querySelector('[role="listbox"]');
          if (menu && !menu.contains(event.target as Node)) {
            preventCloseRef.current = false;
            setIsMenuOpen(false);
          }
        }
      }
    };

    document.addEventListener('mousedown', handleClickOutside);

    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, []);

  const handleChange = useCallback(
    (newValue: any) => {
      setValue(newValue);

      preventCloseRef.current = false;
      setIsMenuOpen(false);

      setTimeout(() => {
        const firstOption = document.querySelector('[role="option"]') as HTMLElement;
        if (firstOption) {
          firstOption.setAttribute('tabindex', '0');
          firstOption.focus();
        }
      }, 100);

      onValueChange?.(newValue);
    },
    [onValueChange]
  );

  const handleMenuClose = useCallback(() => {
    if (preventCloseRef.current) {
      return;
    }

    setIsMenuOpen(false);
  }, []);

  const handleMenuOpen = useCallback(() => {
    preventCloseRef.current = true;
    setIsMenuOpen(true);

    setTimeout(() => {
      const firstOption = document.querySelector('[role="option"]') as HTMLElement;
      if (firstOption) {
        firstOption.setAttribute('tabindex', '0');
        firstOption.focus();
      }
    }, 100);
  }, []);

  const handleKeyDown = useCallback(
    (event: React.KeyboardEvent) => {
      if (event.key === 'Escape') {
        preventCloseRef.current = false;
        setIsMenuOpen(false);
      }
    },
    []
  );

  return {
    value,
    isMenuOpen:isMenuOpen,
    selectRef,
    handleChange,
    handleMenuClose,
    handleKeyDown,
    handleMenuOpen,
  };
};

Metadata

Metadata

Assignees

No one assigned

    Labels

    issue/bug-unconfirmedIssues that describe a bug that hasn't been confirmed by a maintainer yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions