Web Accessibility: Semantic HTML & ARIAs

Marshal Murphy
May 04, 2020 - 7 MIN READ

In a previous article, Web Accessibility: Tabbing & Focus, we tackled accessibility and how to build a page that is 100% accessible to keyboards.

This time around, we are going to dive into how writing semantic HTML does a huge amount of the heavy lifting for us when it comes to accessibility, and how we can use ARIAs to build a web page that is completely accessible to both keyboard and screen-readers.

To demonstrate this, we will create a *Preferences Page** that uses a custom toggle button component in React, applying all the necessary labels, tab indexes and ARIAs needed assistive technologies.

And finally, I will show you how to run an accessibility audit of our webpage using Google Lighthouse.

Let’s get started!


Semantic HTML

Writing semantic HTML simply means using HTML in the manner that they are intended, as opposed to using only divs.

When creating a button or footer, use the <button> and <footer> tags.

Headers should use h1...h6, list items <li>, paragraphs <p>, and so on. Skim this reference on W3Schools for all the possible tags.

The reason we should be using the proper HTML tags is because they provide information to the browser about what they are.

Using divs to build the structure of your web page is entirely acceptable; I do this daily. However, a div provides absolutely zero information to the browser about what the content of that div is.

A button created using a div may look the exact same as <button> due to CSS, yet the browser does not know that it is a button or what the button does.

For assistive technology like screen readers, the elements used provide valuable information to be communicated to the user.

It is our jobs to ensure we build an easily accessible UI, and the first step is using proper HTML tags whenever possible.

Labels for the Non-Obvious

In addition to building a semantic DOM, we need to ensure that elements that are not text level have appropriate labels.

A block of text, for example, will simply be read aloud by a screen reader, and therefore a label is not necessary. A checkbox on the other hand does not convey to a non-visual user information about what it is.

For this, we must implement labels.

There are 2 ways of labelling an element semantically (that is, without using ARIAs, which is coming up next).

One way is to nest the input element inside a label element and provide text for the label:

Another way would be to pass the id of the input element to a for= attribute on a label element:

Text Alternative for Images

Finally, we have alts. Every image should have an alt= attribute, whereby we provide a string to be used in place of an image if the image does not render properly or is being read aloud by a screen reader.

The general guidelines for writing text alternatives to images are:

  • If the image is important because it conveys valuable information to the user, provide a string that concisely describes the image.
  • If the image is decorative, provide an empty string.

A decorative magnifying glass icon within a search field does not need a text alternative, as a screen reader will convey to the user that the input field is for search.

Conversely, a graph demonstrating the effect of a and b over time conveys important data to the user and should be described in the alt tag.


I always found ARIAs to be intimidating. What are they? When do I need them? What do they even do?

ARIAs are basically just attributes we can add to an element that implement additional functionality to the element without changing the DOM.

A checkbox, for example, does not convey to a screen reader when its default checked state changes. To solve this we would add aria-checked=’...’, updating the value of the aria as the checkbox changes state.

Some other examples of aria usage:

  • For labelling an element we can add the attributes aria-label or aria-labelledby.
  • We can fine-tune the experience for assistive technologies with aria-hidden and aria-live.
  • And we can tell assistive technologies when an element that is separate in the DOM should be treated as a child of the current element using aria-owns.

For a full list of ARIA techniques, see the Mozilla web docs.

Build an Accessible Preferences Page

Let’s do something fun now, yes?

We are going to convert a checkbox into a custom animated toggle button using only CSS. Then we will add the necessary Javascript to update the aria-checked state to accurate reflect the state of the element.

Finally, we will reuse the toggle component a handful of times to create an accessible Preferences page.

Let’s begin by pulling down the code from the previous article, and together we will build this new component.

> git clone https://github.com/marshallmurphy/accessibility-part-2
> cd accessibility-part-2
> npm install
> npm start

You will now be able to open your browser to localhost:3000 to view the UI we built last time.

First off, remove the <a> element in the div with the className={styles.body} and replace with the following:

… and update the .body CSS class like so:

We now have a very basic Preferences title below our nav bar.


Now let’s generate a list of elements by mapping over a dummied array off options and render a new ToggleRow component for each.

Rewrite App.js as follows:

…update app.module.css with the new style for preferences …

… and create a new file called src/ToggleRow.js, laying it out to return the array item passed to it as option:

Excellent, an ugly list!

Next, let’s create a custom toggle button using the checkbox input element. This is achieved using mainly CSS and the pseudo-selector :before.

First, create a new file called src/toggleRow.module.css , populate it with the following classes, and import it into ToggleRow.js.

Now let’s create this toggle button by rewriting ToggleRow.js like so:

Looking at the ToggleRow component, you can see that we have created a toggle button that can be set active / inactive using React Hooks for managing state. This state is localized to each individual component, and looks like this:


Pretty neat how we can achieve these styles and animations without ANY Javascript (save for setting state 😜). CSS can do some truly incredible things!

Anyways, while this is great, it is not yet accessible.

  • We cannot access these toggle buttons by keyboard (tab).
  • Labels are not applied to the toggle buttons.
  • The toggle state is not reflected adequately for a screen reader.

Let knock these out and make this a more accessible UI! 💯

Keyboard (Tab) Accessible

I typically like to start with keyboard accessibility first.

In order to be able to tab to each of these toggle buttons, we need to add them to the tab order.

To do this, we need to add tabIndex='0' to the label element, while simultaneously removing <input /> from the tab order by applying tabIndex='-1'.

That’s a great first step, but we need to be able to toggle these buttons by pressing enter once we’ve tabbed to them.

Let’s add an onKeyPress method to the <label> element and a function to handle state for the enter key only, passing the active state to input's checked attribute.

And there we go, keyboard accessible 😁.


Labels & ARIAs

With the keyboard handled, let’s apply labels to the input using aria-labelledby, and represent the checked state with aria-checked.

In App.js, pass the mapping index to the ToggleRow component:

Then, within ToggleRow.js, wrap the {option} within a label element and add an id that incorporates the index to make the id unique to each element.

Note: Assign IDs this way is not ideal. Typically the data you are mapping would be an array of objects with unique IDs that should be used here instead.

Follow this by applying the id to an aria-labelledby attribute on the <input> element:

Lastly, with our labels taken care of, the final task now is to add an aria-checked attribute to the input in order to communicate to screen readers the current state of the toggle.

To do this, simply add the active state to aria-checked on the input element:

Final Project repo over here!

Conducting a UI Accessibility Audit

With all the work we did ensuring that our UI is both keyboard and screen-reader accessible, where would we be if we conduct an audit?

Thankfully, Google Chrome offers this right in the dev tools with Google Lighthouse.

Open up the inspector (mac: cmd + option + i ) and select Audits from the bar with console, elements, network, etc. Select only Accessibility, and click Generate Report.


Well? How’d we do?


There we have it! A fully accessible (albeit simple) UI using semantic HTML, labels, and ARIAs.

While there are other finer points to be covered on the design front, such as ensuring adequate colour contrast, you can know that as a web or front-end developer, creating an accessible UI really isn’t that complex.

Marshal Murphy 2020