Search code examples
htmlaccessibilityhtml-listssemantic-markupscreen-readers

Accessibility of an ordered accordion list with a non-list-item interactive divider block


Background

There is a list of frequently asked questions and answers in the form of an accordion UI element:

<h1>Frequently Asked Questions</h1>
<ol class="accordion">
  <li>
    <h2>
      <button type="button" aria-expanded="false" class="accordion-trigger"
        aria-controls="question-1-answer" id="question-1">
        <span class="accordion-title">What is HTML?</span>
      </button>
    </h2>
    <div id="question-1-answer" role="region" aria-labelledby="question-1"
      class="accordion-panel" hidden>HyperText Markup Language</div>
  </li>

  <li>
    <h2>
      <button type="button" aria-expanded="false" class="accordion-trigger"
        aria-controls="question-2-answer" id="question-2">
        <span class="accordion-title">What is CSS?</span>
      </button>
    </h2>
    <div id="question-2-answer" role="region" aria-labelledby="question-1"
      class="accordion-panel" hidden>Cascading Style Sheets</div>
  </li>

  <!-- list divider start -->
  <div class="accordion-divider" role="region" aria-labelledby="divider-heading">
    <h2 id="divider-heading">Contact us to find out more</h2>
  </div>
  <!-- list divider end -->

  <li>
    <h2>
      <button type="button" aria-expanded="false" class="accordion-trigger"
        aria-controls="question-3-answer" id="question-3">
        <span class="accordion-title">What is JS?</span>
      </button>
    </h2>
    <div id="question-3-answer" role="region" aria-labelledby="question-3"
      class="accordion-panel" hidden>JavaScript</div>
  </li>
</ol>

Between elements 2 and 3, there is a divider block which contains interactive content not relative to the list context (one could imagine an advertisement between the list elements with a call-to-action button).

Problems

  1. Screen reader (tested on macOS's VoiceOver) announces the list as containing 4 items when there should be 3 items and a divider block.
  2. Screen reader should announce the divider block as independent content from the list.

Questions

  • How to accessibly describe that divider block so that the screen reader announces it correctly?
  • Maybe there is a better way to semantically and accessibly describe an ordered accordion list with a divider block?

What I have tried already?

I have tried several WAI-ARIA roles on the divider block (such as region and separator), but the screen reader always announces the divider block as a part of the list.


Solution

  • You don't have valid HTML. It's hard enough getting semantically correct HTML to be accessible but when you have invalid HTML, it's next to impossible.

    The spec for lists says the <ol> can only have <li> elements as direct children. Having a <div> as child of an <ol> will confuse the browser and the screen reader. You can't have a non-list thing in the middle of the list items.

    If you really want something to look like there's a block in the middle of the list, I can think of two three four things to do:

    1. Insert the block at the end (or beginning) of a list item so it visually looks like it's not part of the list but it really will be. I think this would still be confusing for screen reader users because it'll be hard to find the block

    2. Have the block either before or after the list in the DOM but use CSS to move it "inside" the list. The block would really be "on top" (as in z-index) of the list and not really "inside" it.

    3. I suppose a third alternative is to go back to the designers and ask why you want a block in the middle of a list.

    4. And lastly, which should actually solve your problem, create a list with two items (or however many you need before the block) and then close the list and have your block. Then create your third list item as a standalone listitem and point your first list to this "orphaned" list item via aria-owns.

    You create an orphaned list by setting role="presentation" on it but having the first list take ownership of the second list's items.

    You need to re-add the role="listitem" back to the list item because the parent's role="presentation" made the list item a presentational element because role="presentation" is propagated down to the children. See the spec for role="presentation", in particular:

    When an explicit...role of presentation is applied to an element...that has required owned elements, in addition to the element with the explicit role of presentation, the user agent MUST apply an inherited role of presentation to any owned elements that do not have an explicit role defined.

    That is, setting role="presentation" on a list will apply role="presentation" to the listitems (owned elements) as well.

    <ol aria-owns="orphan">
      <li>...What is HTML...</li>
      <li>...What is CSS...</li>
    </ol>
    
    <!-- block of stuff -->
    Contact us
    <!-- block of stuff -->
    
    <ol role="presentation">
      <li role="listitem" id="orphan">...What is JS...</li>
    </ol>
    
    

    You will probably have to use CSS counter() and counter-increment if you want the orphaned list to continue with numbers from your first list.