I'm currently building out a responsive component comprised of three unordered lists, each with an associated heading. The code looks something like this:
<div>
<section aria-labelledby="heading1">
<h3 id="heading1" data-state="open">Heading 1</h3>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
<section aria-labelledby="heading2">
<h3 id="heading2" data-state="open">Heading 2</h3>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
<section aria-labelledby="heading3">
<h3 id="heading3" data-state="open">Heading 3</h3>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
</div>
On larger screens, the CSS renders this as three columns and on smaller screens, it turns into an accordion where clicking on a heading will toggle the display of the adjacent list. The open/close functionality is handled by Javascript and simply toggles the data state between open and closed.
This is all working fine, but when it comes to accessibility I'm a little stumped. I'd like to communicate to assistive technology that the headings are buttons (either using role="button" or by wrapping the heading text in a button element) but this would only be correct when displaying as an accordion. In three-column mode, the headings should not be buttons, just headings.
Any help would be greatly appreciated!
So the DOM stays the same? No additional HTML elements are added in responsive view?
If so, then that's a little tricky but potentially you could work with the Accordion Design Pattern and then tweak the properties on the elements when it's in fullscreen mode.
Accordions consist of:
The title of each accordion header is contained in a <button>
or an element with role="button"
.
Each accordion header button is wrapped in an <hX>
element with a level that is appropriate for the information architecture of the page. The button element is the only element inside the heading element.
If the accordion panel associated with an accordion header is visible, the header button element has aria-expanded
set to "true". If the panel is not visible, aria-expanded
is set to "false". The panel itself should have aria-hidden
set appropriately or hidden with CSS (display:none
).
The accordion panel has role="region"
and aria-labelledby
with a value that refers to the button that controls display of the panel.
This is very close to the code you posted. You have a container for your list (<section>
) which has a default role of region
and you are labeling that section by pointing it to the heading.
So once you have the accordion code, then you can decide what parts of that structure are not needed when the display is just a simple three column list.
<!-- main accordion container -->
<div>
<h2>
<button aria-expanded="false" id="accordion1id">first accordion section</button>
</h2>
<section aria-labelledby="accordion1id">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
<h2>
<button aria-expanded="false" id="accordion2id">second accordion section</button>
</h2>
<section aria-labelledby="accordion2id">
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
</div>
This is really close to your original code. When you only need the list structures (desktop view), you should be able to add role="presentation"
to all the <button>
elements which removes the "buttonness" of the elements and essentially turns them into plain text so all you're left with is a heading with child text. A screen reader will only know there is a heading followed by a section with an embedded list.
You should remove aria-expanded
from the buttons too since that attribute is only valid on a button and not a plain text element.
If you wanted, you could also remove the aria-labelledby
on the <section>
and that would remove the landmark
role for those elements so they'll be ignored by screen readers too. The code would look something like this:
<!-- main accordion container -->
<div>
<h2>
<button role="presentation" id="accordion1id">first accordion section</button>
</h2>
<section>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
<h2>
<button role="presentation" id="accordion2id">second accordion section</button>
</h2>
<section>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</section>
</div>
What the screen reader sees will effectively be:
<div>
<h2>
first accordion section
</h2>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<h2>
second accordion section
</h2>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
</div>
Update
Thanks, @Robin, you are correct. ARIA attributes are only hints to assistive technology and don't affect the behavior so adding role="presentation"
to the <button>
, while it does make the button not announce as a button anymore, that doesn't prevent a user from tabbing to it or clicking on it so you'd need tabindex="-1"
on the button to prevent tabbing and you'd need to handle events on the button as NO-OPs. You also might need to apply CSS so the button doesn't look like a button either. That's all doable but is sometimes easier to just swap in and out different components.