Search code examples
reactjsaccessibilitywai-aria

aria-describedby not working with role="listitem" on Safari with VoiceOver


I'm trying to make a list accessible using role="list" as the parent and role="listitem" for the items. I want each list item to have a description using aria-describedby in addition to its text content.

Here is a simplified version of my code:

<div class="wrapper">
  <div id="rbd-hidden-text-11-hidden-text-132" style={{ display: "none" }}>
    This text should be read by screen reader
  </div>
  <div role="list" style={listStyle}>
    {items.map((item, index) => (
      <div
        key={index}
        // NOTE: using listitem, you can focus item's focusable
        // children like buttons but aria-describedby is not read on
        // macOS
        role="listitem"
        // Button role makes aria-describedby readable by macOS VO
        // but you cannot focus its focusable children :(
        // role="button"
        style={listItemStyle}
        tabIndex={0}
        // macOS reads this only when role="button"
        aria-describedby="rbd-hidden-text-11-hidden-text-132"
        // aria-labelledby="rbd-hidden-text-11-hidden-text-132"
        // aria-description="Is this read by a screen reader?"
      >
        {item}
        &nbsp;
        <button>Action1</button>
        &nbsp;
        <button>Action2</button>
      </div>
    ))}
  </div>
</div>

When testing on macOS Chrome, everything works fine. When a list item is focused by VoiceOver, the description is read as expected. However, this does not work on Safari.

Changing the role to button makes aria-describedby readable by VoiceOver on macOS, but introduces another issue: elements with role="button" prevent VoiceOver from focusing on interactive children.

My Questions:

  1. How can I get aria-describedby to work with role="listitem" on Safari with VoiceOver?
  2. Is there a workaround to make aria-describedby work without changing the role to button and still allowing interactive children to be focused?

Any help or suggestions would be greatly appreciated!

CodeSandbox demo


Solution

  • I don't have direct answers to your questions, but some elements in your code makes the bell ring to me:

    1. Your aria-describedby refers to an hidden element with display:none. This sends a contradicting signal to the screen reader: should it be read because it's a description of something else, or it shouldn't be read because of display:none ? While it may work with some OS+browser+screen reader combos, you are safer if you consider that display:none wins over everything (and thus it won't be read). Same thing for visibility:hidden or hidden HTML5 attribute. You should try .sr_only/.visually-hidden text instead.
    2. You have a nested focus problem: you have a parent element with tabindex=0, and several buttons inside it. This is an accessibility anti-pattern, I have already explained why in several other SO answers. You should probably remove tabindex=0 on the parent and only keep the children focusable.
    3. Attributes aria-label, aria-labelledby, aria-description and aria-describedby aren't guaranteed to work if they aren't used with elements that are whether interactive/focusable or landmarks, and if those elements haven't a role corresponding to such kind of functions.
    4. Roles list and listitem aren't supposed to be applied on focusable elements. Their natural corresponding elements are <ul>, <ol> and <li>. You might try roles listbox/option, toolbar/button, or menu/menuitem for example. As a reminder, try to take the appropriate element from the start without using role attribute at all if you can.

    This makes four good possible reasons not to read descriptions as you expect.