Skip to content

Instantly share code, notes, and snippets.

@jutunen
Last active July 28, 2022 07:29
Show Gist options
  • Save jutunen/d8c3948656f449cdb1da8addaac0c766 to your computer and use it in GitHub Desktop.
Save jutunen/d8c3948656f449cdb1da8addaac0c766 to your computer and use it in GitHub Desktop.
Semantic UI React Dropdown accessibility fix

Semantic UI React Dropdown accessibility fix

All screen readers can't handle SUI Dropdowns correctly as this issue points out.

In the following I'll show how to fix SUI React so that NVDA screen reader can read out keyboard controlled SUI Dropdown menu items.

1. Add aria-activedescendant prop to Dropdown component:

    <Dropdown
       options={options}
       aria-activedescendant={activedescendant}
       onKeyDown={(ev, data) => handleOnKeyDown(ev,data)}
    />

2. Add state to control the prop value:

    const [activedescendant, setActivedescendant] = useState("");

3. Provide ids for menu items via options prop:

    const options = [
        {
            key: "1st",
            text: "First option",
            value: "my_first_value",
            id: "any_id_one",
        },
        {
            key: "2nd",
            text: "2nd option",
            value: "my_2nd_value",
            id: "any_id_two",
        },
    ];

4. Set activedescentant in onKeyDown handler:

    function handleOnKeyDown(ev, data) {
        setActivedescendant(data);
    }

Next we will modify the Dropdown component's source code so that onKeyDown event will provide the current selected item value to the event handler. Note that we'll be modifying the ES compiled code of the component.

The Dropdown.js file to be modified should be found from node_modules\semantic-ui-react\dist\es\modules\Dropdown folder.

5. Modify moveSelectionOnKeyDown method so that it returns nextIndex:

    _this.moveSelectionOnKeyDown = function (e) {
      var _moves;

      var _this$props2 = _this.props,
          multiple = _this$props2.multiple,
          selectOnNavigation = _this$props2.selectOnNavigation;
      var open = _this.state.open;

      if (!open) {
        return;
      }

      var moves = (_moves = {}, _moves[keyboardKey.ArrowDown] = 1, _moves[keyboardKey.ArrowUp] = -1, _moves);
      var move = moves[keyboardKey.getCode(e)];

      if (move === undefined) {
        return;
      }

      e.preventDefault();

      var nextIndex = _this.getSelectedIndexAfterMove(move);

      if (!multiple && selectOnNavigation) {
        _this.makeSelectedItemActive(e, nextIndex);
      }

      _this.setState({
        selectedIndex: nextIndex
      });

      return nextIndex;
    };

6. Modify handleKeyDown method so that it'll provide the item id in event invoking:

    _this.handleKeyDown = function (e) {
      var selectedIndex = _this.moveSelectionOnKeyDown(e);

      _this.openOnArrow(e);

      _this.openOnSpace(e);

      _this.selectItemOnEnter(e);

      var selected = _isNil(selectedIndex) ? _this.state.selectedIndex : selectedIndex;

      var item = _this.getSelectedItem(selected);

      var selectedId = _get(item, 'id');      

      _invoke(_this.props, 'onKeyDown', e, selectedId);
    };

7. Rebuild your project and test it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment