We coded the accordion with accessibility in mind, in contrast, plain text is inherently accessible, by hiding content inside hidden panels we need to make an extra effort to make it perfectly accessible for screen-readers and people who use keyboars to navigate.
Below you can read about the different solutions we used while building the accordion.
Screen readers
In the accordion panels we are using javascript to change the state of the accordion panels. Changing states are usually visually apparent to users who can see the page but they may not be obvious to users of assistive technologies like screen readers. To fill this gap and provide a way to programmatically expose dynamic content changes in a way that can be announced by assistive technologies we can use Accessible Rich Internet Applications (ARIA) attributes.
Below is an example of the code with an explanation of the attributes
Making the buttons accessible
By adding role="button"
we will explain to a screen reader that the accordion-heading has a button control. We also need to indicate whether the collapsible content below is in the extended or in the collapsed state. We can do this by using the aria-expanded
attribute
Our panel can be in one of two states:
aria-expanded=false
The block is collapsed so the content is not visible for the user.
aria-expanded=true
The block is expanded and the content is visible for the user.
Creating the relationship between the button and the panel
We need to explain that the heading and the panel are related to each other. We can do this by using the aria-controls
to explain that this element has control over and affects the panel. And in the panel we can use the aria-labeledby
attribute to describe the relationship and explain that this element is revealed by the header
aria-controls="accordion_1"
points to the ID of the panel which the header controls. In the panel you see the aria-labelledby="accordion_1"
this attribute establishes relationships between the panel and the header.
Making the Panel accessible
For the panel we are using a div
and to explain the role of this div as a content area that has relevant information related to the heading we use role="region"
.
Source:
https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html
Keyboard
Keyboard accessibility is one of the most important aspects of web accessibility. Nowadays, when most web interfaces are designed with mouse cursors and touch interaction in mind, keyboard navigation is neglected.
The keyboard is still the primary means for a large group of users to navigate websites so we want to make sure that users can navigate efficiently and unhindered using just their keyboard.
Space or Enter | When focus is on the accordion header of a collapsed section, expands the section. |
---|---|
Tab |
|
Shift + Tab |
|
Down Arrow |
|
Up Arrow |
|
Home | When focus is on an accordion header, moves focus to the first accordion header. |
End | When focus is on an accordion header, moves focus to the last accordion header. |
Helping keyboard users to focus the panels
inside the Panels we have elements and text that users can interact with, examples are input fields or long texts that they need to scroll. Keyboard users who browse the accordion need to be able to to focus on the panel itself to get access to these elements.
To make sure the panel can get focus successfully in all browsers we need to add the special case tabindex="-1"
attribute to the panel itself. This attribute value prevents the panel from being part of the tab order of the page, but allows focus to be placed on it by scripting. So this means we can only focus to the panel itself when it is opened.
When the focus is on the panel they can easily have access to the elements inside the panel, they can access form fields or scroll text.
Keydown script
To make this work we created a key-down script that checks which key is pressed and then we do one function or other.
$(document).keydown(function (e) {
// Space 32
// Enter 13
// TAB 9
// Shift 16
// Down 40
// Up 38
// Home 36
// End 35
let current = $(":focus");
//let li_elem = current.closest("li.awesome-accordion-item");
switch (e.which) {
case 38: // up
move_up_down(-1);
break;
case 40: // down
move_up_down(1);
break;
case 36: // Home
go_home_end(-1);
break;
case 35: // End
go_home_end(1);
break;
default:
return; // exit this handler for other keys
}
if( current.closest("ul.awesome-accordion").length > 0 && current.closest(".awesome-accordion-panel").length < 1 )
{
e.preventDefault(); // prevent the default action (scroll / move caret)
}
});
Move focus using arrow keys
Here move_up_down and go_home_end. Depends if is up/down we change the focus to right element
function move_up_down(action)
{
let current = $(":focus");
if( current.hasClass("js-open-close-acc") )
{
let ind = $(".js-open-close-acc").index(current);
ind = ind + action;
if( ind === $(".js-open-close-acc").length )
{
ind = 0;
}
else if( ind < 0 ) { ind = $(".js-open-close-acc").length - 1; } $(".js-open-close-acc:eq(" + ind + ")").focus(); } } function go_home_end(action) { console.log("go") let current = $(":focus"); if( current.hasClass("js-open-close-acc") ) { var ind = 0; if( action > 0 )
{
ind = $(".js-open-close-acc").length -1;
}
$(".js-open-close-acc:eq(" + ind + ")").focus();
}
}
Further reading
Below a list of online resources that are useful if you want to know more about accessibility:
https://www.w3.org/TR/wai-aria-practices/#accordion
http://web-accessibility.carnegiemuseums.org/code/accordions/
Progressive enhancement
When we start building something we try to work in the simples, most accessible environment (browser with javascript disabled) From there we progressively enhance the accordion with features that will improve the user experience in browsers that support them, or silently fail in browsers that don't.
What this means for our accordion is that the core function of the accordion the expand/collapse version should also work when javascript is disabled. We solved this by adding a default 'no-js' class in the accordion that is removed when Javascript is available.
JS part
if( $(".awesome-accordion").length > 0 )
{
$(".awesome-accordion.no-js").removeClass("no-js");
prevent_focusable();
}
CSS part
.awesome-accordion.no-js{
li.awesome-accordion-item{
overflow: hidden;
}
& .awesome-accordion-panel {
max-height: 400px;
}
input[type='checkbox'].no-js-action{
display: block;
position: relative;
z-index: 1;
float: left;
width: 100%;
height: 50px;
margin-bottom: -50px;
cursor: pointer;
opacity: 0;
}
input[type='checkbox']:checked ~ .awesome-accordion-panel{
position: relative;
z-index: 0;
margin-top: 0;
max-height: 0;
opacity: 0;
transform: translate(0, 50%);
transition: 0.25s linear;
padding: 0;
}
.awesome-accordion-panel h2{
margin-bottom: -65px;
}
}