Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dropdowns are inaccessible for screen readers #243

Open
James1x0 opened this issue Jan 10, 2019 · 3 comments
Open

Dropdowns are inaccessible for screen readers #243

James1x0 opened this issue Jan 10, 2019 · 3 comments

Comments

@James1x0
Copy link

James1x0 commented Jan 10, 2019

Based on the custom nature of dropdowns, dropdown selects are pretty useless when it comes to screen readers.

screenreaderariasuidrop

SR needs tags like aria-activedescendant and role="listbox", which are rollable with markup...

{{#ui-dropdown
  onChange=(action (mut value))
  as |execute mapper|}}
  <div class="default text">
    --Select--
  </div>
  <i class="dropdown icon"></i>
  <ul id="anId" class="menu" role="listbox" aria-activedescendant="anId__{{value}}">
    {{#each itemArray as |item|}}
      <li id="anId__{{item}}"
        class="item"
        role="option"
        data-value="{{map-value mapper item}}">
        {{item}}
      </li>
    {{/each}}
  </ul>
{{/ui-dropdown}}

But, the listbox is triggered by js, which for aria has to be a popup. To do this, we would need to bind a aria-haspopup attr to the triggering button to reference the listbox menu, and in tandem set aria-expanded to true. Without introducing an ember-specific anti-pattern, this seems impossible to implement.

I'm definitely open to hearing ideas here if someone has figured this out.

@James1x0
Copy link
Author

Related Semantic-Org/Semantic-UI#348

@rachael-ross
Copy link

rachael-ross commented Feb 6, 2019

Yes, they are. We have a blind customer that cannot complete their tasks due to the dropdown not responding to same keyboard events that a "select" normally does. It receives the focus on tab and the reader reads the option text, however, using arrow keys doesn't move the focus from "option" to "option" (really divs) and hitting Enter on the keyboard, of course, then doesn't select the item the blind user would like to select. Any workarounds or plans to address this?

@James1x0
Copy link
Author

James1x0 commented Feb 6, 2019

@rachael-ross We obviously needed some solution to this, and I came up with a workaround. It's not perfect, but it works with my testing.

I came up with a tweak function we use in an input wrapper component (it can also render other inputs).

  dropdownAriaTweaks (type) {
    let $dropdown = this.$('.ui.dropdown');

    if (type === 'init') {
      $dropdown.attr({
        'aria-haspopup':   'listbox',
        'aria-labelledby': `${this.get('inputId')}__text ${this.get('inputId')}__label`,
        role:              'button'
      });
    } else if (type === 'show') {
      setTimeout(() => {
        $dropdown.attr('aria-expanded', true);
        let $menu =  this.$('ul[role="listbox"]')[0];
        console.log('focusing ', $menu);
        this.set('adtIsFocusing', true);
        $menu.focus();
      }, 300);

      if (this.get('adtIsFocusing')) {
        this.set('adtIsFocusing', false);
        return false;
      }
    } else if (type === 'hide') {
      if (this.get('adtIsFocusing')) {
        this.set('adtIsFocusing', false);
        return false;
      }

      setTimeout(() => {
        $dropdown.attr('aria-expanded', false);
        let $focused = document.activeElement || {};

        let totalFocusLoss = $focused.id && !this.$(`#${$focused.id}`)[0];

        if (!this.get('adtIsFocusing') && !totalFocusLoss) {
          this.set('adtIsFocusing', true);
          $dropdown.focus();
        }
      }, 300);
    }
  }

Our dropdown usage looks like this. Please note this is highly tailored to our usage.

  {{#ui-dropdown
    aria-haspopup=inputId
    class=inputClass
    selected=(readonly (get model field.path))
    forceSelection=false
    showOnFocus=field.inputAttrs.showOnFocus
    allowAdditions=(if field.inputAttrs.allowAdditions true false)
    onShow=(action dropdownAriaTweaks "show")
    onHide=(action dropdownAriaTweaks "hide")
    onChange=(action (mut value))
    debug=true
    as |execute mapper|}}
    <div id="{{inputId}}__text" class="default text" {{action showDropdown}}>
      {{#if field.selectText}}
        {{field.selectText}}
      {{else}}
        --Select--
      {{/if}}
    </div>
    <i class="dropdown icon"></i>
    <ul
      id="{{inputId}}"
      tabindex="-1"
      aria-labelledby="{{inputId}}__text {{inputId}}__label"
      class="menu"
      role="listbox"
      aria-describedby="{{aria.describedBy}}"
      aria-activedescendant="{{if (not-eq value undefined) (join '__' (array inputId value))}}">
      {{#each (get this field.contentPath) as |item|}}
        <li
          id="{{inputId}}__{{if field.valuePath (get item field.valuePath) item}}"
          class="item"
          role="option"
          data-value="{{map-value mapper (if field.valuePath (get item field.valuePath) item)}}"
          aria-selected="{{eq value (if field.valuePath (get item field.valuePath) item)}}">
          {{#if field.displayKey}}
            {{get item field.displayKey}}
          {{else}}
            {{item}}
          {{/if}}
        </li>
      {{/each}}
    </ul>
  {{/ui-dropdown}}

I hope this helps.

EDIT: Forgot to mention we call the init tweak portion of that function in didReceiveAttrs:

  didReceiveAttrs () {
    scheduleOnce('afterRender', () => this.dropdownAriaTweaks('init'));
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants