How to Make an Accordion FAQ with JS - part 2

Part 2: let's get accessible

This is part two of my three-part accordion FAQ tutorial. Part two is on accessibility, part three is on making the project look pretty. Why? Because I believe accessibility (a11y) is more important, making sure everyone can use something is more important than how it looks.

And that isn't to say I'm an expert on a11y, I'm not. I am, however, constantly working to improve and continue to make it a priority in my code.

At the end of part one, we had a very bare project:

HTML

<article class="q">
  <div class="title">
    <h2>Question One</h2>
       <button>Clicky</button>
   </div>
   <div class="answer">
       <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  </div>
</article>

SCSS

.answer {
    display: none;
}
.clicked {
    .answer {
        display: block;
  }
}

JavaScript

const question=document.querySelectorAll('.q');

question.forEach(function (quest) {
  const btn=quest.querySelector('button');
  btn.addEventListener('click', function() {
    quest.classList.toggle('clicked');
  });

});

We won't be adding much code for this step. Why? By using HTML5 elements, we've already achieved greater a11y. We structured our project using elements that are meaningful to assistive technology. Each question is an article, and in my code, (where this is a stand-alone project) the questions are wrapped in <main>. If this is part of a page with more information, wrapping them in <section> nested inside <main> would be best.

These elements are helpful for assistive technology, as opposed to using just <div> elements that don't automatically provide structure.

Adding an ARIA Attribute

We need to add code so that assistive technology can tell the user if an answer is showing or not. We do that by adding the aria-expanded attribute to our <article>, like so:

<article class="q" aria-expanded="false">

This will let users know there's more information that isn't currently visible. Doing this alone is not enough, we have to toggle the attribute to "true" when the answer is showing. To do that, we go back to the JavaScriptmobile!

JavaScriptmobile

Inside our eventListener we need to declare a variable for the aria-expanded attribute of the specific question (quest) that is clicked, and we need to use getAttribute to do so.

let ariaValue=quest.getAttribute("aria-expanded");

If we'd print ariaValue to the console right now, it would display "false", no matter how many times we click the button. To remedy that, we'll use a ternary operator. A ternary operator looks at a condition and executes an expression if it's true, a different expression if it's false. In our case, it will look to see if the value of the aria-expanded attribute is true or false, and then set it to the opposite. Keep this in mind as this next part may be confusing at first.

ariaValue = (ariaValue === 'false') 
     ? quest.setAttribute('aria-expanded', 'true') 
     : quest.setAttribute('aria-expanded', 'false');

Now let's break this down a little: ariaValue = is just us wanting to store something in that variable. ariaValue === "false" ? is the condition that's being evaluated, if it evaluates true (meaning the current value is "false"), then aria-expanded is set as true.

If the condition evaluates false (meaning the current value is "true"), then our attribute is set to false. If you're thinking I'm trying to tell you that if something is false, and it should be true it gets set to true, and if something is supposed to be false, and it is false that it's false, it gets set to false anyway... You're correct!

Here's a less maddening example:

var skyColor = 'blue';
var timeOfDay = (skyColor === 'blue') ?  'day' : 'night';
console.log(timeofDay);  //'day'

Currently, skyColor is blue, because that's what the variable is initialized as. The second variable, timeOfDay is going to evaluate the condition (skyColor === 'blue'), and if that expression is found to be true, timeOfDay stores day.

var skyColor = 'black';
var timeOfDay = (skyColor === 'blue') ?  'day' : 'night';
console.log(timeofDay);  //'night'

In this example, the expression is false, so timeOfDay stores 'night'.

If your brain isn't a tangled mess after all that, congratulations! You're done! If it is a tangled mess, congratulations! You're still done! And here's a bandage for your brain.

Visual Effects

Here's a gif of a button being clicked and the aria-expanded value changing (our project doesn't look like this yet):

FAQ-aria.gif

I really hope you enjoyed this article and found it helpful. Leave a comment below if you have any questions. Tune in next week for when we style this project!

Did you find this article valuable?

Support AC Hulslander by becoming a sponsor. Any amount is appreciated!