I'm working on making our accordions accessible with aria labels such as aria-expanded. I have the value of aria-expanded change correctly as the accordion trigger heading gets clicked or the 'return' button is pressed on the keyboard. For some reason though ChromeVox, which I'm using to test, will only say 'button collapsed' initially but doesn't say 'button expanded' once the value changes after a click.
I've looked at examples on other sites such as https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html and ChromeVox reads both states of aria-expanded correctly there, so I'm thinking it's something about how my code is structured that is preventing ChromeVox from announcing the 'expanded' state.
Here is an example of one accordion section:
<div class="accordion-section">
<button tabIndex="0" id="rules_list" class="accordion" aria-expanded="false" aria-controls="sectRules">
<span class="title">Rules</span>
</button>
<div class="content" role="region" id="sectRules" aria-labledby="rules_list">
<h4>What rules must all visitors follow?</h4>
<ul class="list">
<li style="list-style-type:disc; border-top:0px; margin-bottom:0px; padding-bottom:0px; padding-top:10px; overflow:visible;">rule 1</li>
<li style="list-style-type:disc; border-top:0px; margin-bottom:0px; padding-bottom:0px; padding-top:10px; overflow:visible;">rule 2</li>
<li style="list-style-type:disc; border-top:0px; margin-bottom:0px; padding-bottom:0px; padding-top:10px; overflow:visible;">rule 3 etc..</li>
</ul>
</div>
</div>
The relevant js is:
/* Generic Accordion */
$('.accordion .title').click(function() {
$(this).parent().parent().children('.content').toggle();
$(this).toggleClass('open');
$(this).hasClass("open") ? $(this).parent().attr("aria-expanded", "true") : $(this).parent().attr("aria-expanded", "false");
});
/* Adding support for keyboard */
$('.accordion').on('keydown', function(e) {
if (/^(13|32)$/.test(e.which)) {
e.preventDefault();
$(this).find('.title').click();
}
});
The relevant CSS is :
.accordion + .content {
display: none;
margin-top: 10px;
padding: 10px;
}
.accordion + .content.open {
display: block;
background-color: white;
}
I'm at a loss, any help would be much appreciated.
I second what brennanyoung said about the way you used the span
element and why it probably is the reason your code doesn't work as you expect.
In my opinon, you really should consider using the button
element to toggle the content, as this will avoid some extra work such as:
span
covers the whole button to prevent clicking the button which would result in nothing (saying it aloud sounds weird),span
,span
properly acts like a button
(click, press, and other button
related events).Also, programmatically triggering the click
event in your code is a hint that something easier can be done.
hidden
+ aria-labelledby
I would tend to keep it as simple as possible by using both the hidden
attribute on the content to be toggled, and the aria-expanded
on the button toggling it.
Collapsible Sections of the "Inclusive components book" by Heydon Pickering is a very good read if you need… well, collapsible sections. Actually the whole book is awesome, you won't waste your time reading it if you haven't yet.
The hidden
attribute is properly handled by the screen readers and will hide the element both visually and from the accessibility tree. You can use it on pretty much any recent web browser (https://caniuse.com/#search=hidden), which makes it a good candidate to avoid juggling with classes and the CSS display
property.
If you want to use the aria-labelledby
(with 2 "L" by the way, there is one missing in your code) on the content (and you should, since you declared it as a region), using the button
text as a label works fine.
However, if you plan to use a text that describes the action (for example "Show the rules" or "Hide the rules" depending on the state), then this isn't relevant anymore and you will have to use another element as the label for this landmark. The h4
element in your code seems to do the job, and giving it an id
will let screen readers identify the region more easily.
I took the liberty of rewriting the example you provided to only use plain JS, with the small adjustments I mentioned. It is testable here.
<div class="accordion-section" id="accordion-1">
<button id="rules-list" class="rules-toggle" aria-expanded="true" aria-controls="sect-rules">
<span>Rules</span>
</button>
<section role="region" class="content" id="sect-rules" aria-labelledby="rules-list-title">
<h4 id="rules-list-title">What rules must all visitors follow?</h4>
<ul class="list">
<li>rule 1</li>
<li>rule 2</li>
<li>rule 3</li>
</ul>
</section>
</div>
const myButton = document.querySelector('#accordion-1 .rules-toggle');
myButton.addEventListener('click', toggleRules);
function toggleRules(evt) {
const button = evt.currentTarget;
const accordionSection = button.parentElement;
const content = accordionSection.querySelector('.content');
if ('hidden' in content.attributes) {
content.removeAttribute('hidden');
button.setAttribute('aria-expanded', true);
} else {
content.setAttribute('hidden', true);
button.setAttribute('aria-expanded', false);
}
}
.rules-toggle + .content {
margin-top: 10px;
padding: 10px;
background-color: white;
}
.list li {
list-style-type: disc;
border-top: 0;
margin-bottom: 0;
padding-bottom: 0;
padding-top: 10px;
overflow: visible;
}
You put the click handler on the span, not the button? That doesn't seem right, and might be the cause of the problem, since aria-expanded
is placed on the button.
Voiceover is probably looking for aria-expanded
on the event target, which is the span
, not the button. Of course it does not find it.
This might explain why it announces the state when the button gets focus, but not when you click it.
So check whether adding click to the button, rather than the span, gives you the result you want. And if you do this, you can skip one of the parent()
steps in the toggle()
.
In addition, I would set aria-expanded
on the .content
and keep it in sync with the aria-expanded
attribute on the button.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With