Accessibility Series: An Unreasonably Long Introduction To ARIA (With Example Implementation)
Let’s Get Started With An Unreasonably Long Preamble
When I first started this blog series (back in 2019, with an 18 month break after the second post 😔) one of my goals was to become an “expert” in ARIA and then write a post on the topic. In an effort to get myself started, I kept looking around on the internet for a quick introduction to ARIA for the web. What I wanted was a five-minute, crib notes version that I could just scan to get the basics; I figured once I had that, I could start peppering in ARIA here and there in my work and fill in the gaps in my knowledge as needed. I never found a cheat sheet like this, and naïvely assumed it was because nobody had written one yet. It was only after I really started to read more about ARIA that I learned that, just as web accessibility is a complicated series of cross-cutting considerations, perhaps ARIA simply does too many things to be covered in a cheat sheet. Perhaps a five minute intro to ARIA would be like creating a five minute intro to speaking French or playing piano.
label statements because in 2021 probably nobody should be using them.
Today, as I write this, I don’t feel like an expert practitioner of ARIA nor am I a professor of ARIA. However, I have been trying to (responsibly) increase my usage of ARIA over the last year and a half, and I’m feeling like a solid ARIA beginner.
Array.prototype.reduce() callback every. single. time. I consider myself an HTML expert, but I certainly haven’t memorized every single HTML element that exists. Did you know that there’s a
<ruby /> tag? I sure didn’t! I’m ten years into professional web development and I just learned about it a week ago!
Rather than rote memorization, I’d assert that expertise is about knowing broadly what nature of things is in a language or API, and understanding how best to utilize them in a manner that is effective, maintainable, and scalable. Furthermore, I’d add that knowing where to look to find the best reference material and how to debug what you have created are other tentpoles of expert status.
All this is to say: I’m not an expert, but I’m learning. And part of the learning process has been to identify what ARIA can do, what it can’t do, what it shouldn’t do, and the other rules and guardrails that need to be adhered to when using it. Since my goal here has been to learn by blogging, I decided to share a top-level, introductory pass to what ARIA is all about, and what resources should be referenced when leveraging ARIA in your projects. It is a bit lengthy, but should hopefully be shorter than reading the spec itself, and provide sufficient information to get you started actually using ARIA in your work. Furthermore, we’ll create two example implementations as an exercise to provide a practical understanding of how ARIA is used.
What Is ARIA
ARIA stands for Accessible Rich Internet Applications. While some web developers may only vaguely know it as “some accessibility thingy” ARIA is a W3C spec that has been in recommended status since 2014. The high-level description is that ARIA is a collection of attributes that can be added to HTML elements in order to change how they are understood and communicated by assistive technology.
We are going to spend a lot of time in this article discussing context and rules for how to use ARIA before we get to examples where we build interactive accessible components using ARIA. If you’re super eager you can skip straight to the examples, but I strongly urge you to make sure you circle back to read the following sections; it might save you from writing bad ARIA, which the W3C tells us is worse than no ARIA at all.
What Can ARIA Do?
Most users who don’t leverage any assistive technology when web browsing are probably unaware that, in addition to the visual display they see on the screen, the browser also renders an “accessibility tree.” This is a sort of overlay on the DOM tree, and contains semantic metadata about the elements on the page. This can include:
- Names and Descriptions
- Roles (such as “checkbox” or “heading”)
- States (like “checked/unchecked,” “expanded/collapsed,” etc)
- Linkages (such as descriptions of when an element controls another element, or contains another element, etc).
This accessibility tree is what is leveraged by screen readers in order to navigate and describe the content on the page. A page with a complete, rich accessibility tree will be more easily accessible to users leveraging a screen reader. A page with an incomplete accessibility tree (or even worse, an incorrect accessibility tree) could be difficult or impossible for a screen reader and/or keyboard user to interact with.
While ideally one leverages proper native elements in a manner that the page’s accessibility tree is rendered automatically in a complete and robust manner, this is sometimes not possible or not pragmatic. In these cases, ARIA can be leveraged in order to manually alter the accessibility tree in a manner that makes the page properly accessible to those using assistive technologies that rely on the accessibility tree. Additionally, ARIA can manage alerting users who cannot see the screen to dynamic content updates that they would otherwise miss (for example, a pop-up notification for a new message).
“ARIA can both cloak and enhance, creating both power and danger.” — WAI-ARIA Authoring Practices 1.1
One crucial word of caution: while ARIA can aid in adding or enhancing accessibility, it can also destroy accessibility. Improper tagging can break native accessibility, or render a page in a confusing or fully unusable manner for assistive technology. This appears to be a major concern for the creators of the ARIA spec; they have a section in the ARIA Authoring Practices document entitled “No ARIA Is Better Than Bad ARIA,” in which they note that “ARIA can both cloak and enhance, creating both power and danger”. Some of these “danger” scenarios are covered in the “Rules of ARIA,” specifically “Rule #2: Never change native semantics” and “Rule #4: Don’t hide focusable elements from the accessibility tree;” however, these don’t encompass all cases of problematic implementations that could negatively impact accessibility. For this reason, it is important to know who is using ARIA and how (more on that later in the article), as well as how to thoroughly test any ARIA implementation in which there is a question as to how it might work.
What *Can’t* ARIA Do?
As I have previously discussed in this series, web accessibility is more than just keyboard and screen reader support. While ARIA can make a difference for users leveraging assistive technologies that interact with the browser’s accessibility tree, it will do nothing for accessibility issues for users not using assistive technology. For instance, text-to-background contrast issues, inaccessible colors for users with color blindness, or motion issues for those with vestibular disorders will be completely unaffected by anything you do with ARIA.
When Should ARIA Be Used?
The “first rule of ARIA” in the spec is “Don’t use ARIA.” This is because much of the functionality ARIA provides can be accomplished without ARIA by simply using proper semantic HTML and best practices. For instance, why use a
role="nav" when using a
<nav/> element would implicitly provide that role to the accessibility tree?
However, this is not always the case. Sometimes you have to create a custom checkbox treatment (or other custom form element) that is impossible to achieve with the native element. Or perhaps you are creating a widget or control that doesn’t have an analogous HTML native element. Or maybe you are leveraging a library that uses inaccessible elements out of the box. For these cases, ARIA can help provide the roles, descriptions, states, and relationships in the accessibility tree that would otherwise be missing.
How Is ARIA Consumed?
One of the biggest challenges of ARIA is that it is consumed by a wide variety of screen readers that have different levels of support for the features ARIA provides. I’ll admit that my experience is mostly limited to VoiceOver on macOS, although the feature set is, I believe, relatively typical of most screen readers. With VoiceOver, you can navigate through the content on the screen using the keyboard. When VoiceOver focuses an element, it will read additional information about the element’s context in the accessibility tree — its role, its state, text that may label or describe it, and how to interact with it using VoiceOver and the keyboard. Furthermore, VoiceOver provides a way to quickly list and interact with page components by type — for instance, VoiceOver can list all anchors on the page, or all headings, or all landmark roles.
Understanding how your page is navigated with a screen reader can be aided by the accessibility dev tools in the browser, but I’d caution against considering it a complete replacement for actually testing with a screen reader. I’ll admit this is an area where I’m learning to practice what I preach. Occasionally I’ll think that I have a page nice and accessible, only to actually try navigating it with VoiceOver and discover issues. While it would also be advisable to test with many different screen readers, this is not always practical or possible — it is worth noting, though, that there are example videos on YouTube of individuals demoing popular screen readers, to give you a sense of how they work.
Who Uses ARIA?
This is an interesting question. As near as I can tell, the general consumer of ARIA tagging is screen readers; other assistive technologies besides screen readers exist, but I don’t believe they are usually leveraging ARIA. However, ARIA tends to go hand-in-hand with keyboard usage; while ARIA won’t change the interactivity for a page, we know that the ARIA spec requires that interactive ARIA elements be usable with the keyboard, and that the instructions for implementing a proper interactive widget with ARIA will include rules for keyboard navigability. So, with this context, we can break down ARIA usage by consideration of who can/cannot see the screen, and who can/cannot use the keyboard, and get some perhaps surprising results:
- Users who interact with the page mostly with the keyboard (or other assistive technology) and cannot see the screen: This is probably the most commonly considered use case. It would encompass users with a visual impairment that makes it difficult or impossible for them to see the content on the screen, who thus use a screen reader and navigate with the keyboard. Their browsing experience is impacted heavily by the accessibility tree of the document, and thus they will be directly consuming widgets implemented with ARIA.
- Users who interact with the page mostly with the keyboard (or other assistive technology) and *can* see the screen: This could be users without any visual impairment who simply prefer using a keyboard, or perhaps who have other disabilities or injuries that prevent them from using a mouse. Although they are not leveraging the screen reader, they rely on the accessibility tree being properly structured and interactivity properly wired for keyboard use, which would be the case for widgets properly implemented with ARIA.
- Users who interact with the page mostly using the mouse and cannot see the screen: I am relatively confident that this user doesn’t exist, or is quite rare; I imagine being unable to see the screen and cursor position would make this a relatively unproductive way to use the web. However, I am happy to be proven wrong if anybody wishes to do so in the comments section.
- Users who interact with the page mostly using the mouse and *can* see the screen: This is probably most web users on a laptop or desktop computer. And most of them probably do not leverage ARIA. However, there is the exception of users who have difficulty reading. This could be users who cannot read due to an impairment like dyslexia, or who are not native speakers of the given language and can speak it but cannot read/write in it, or users have difficulty reading due to injury, or who didn’t learn to read for other reasons. Regardless of why, these users may be using the mouse to highlight text and have the screen reader speak it aloud to them. Most of the time they can manage this just fine without ARIA. However, ARIA can negatively impact their experience if not implemented in a way that considers these users. For instance, text that is hidden from the screen reader using
aria-hiddenand then rendered invisibly for screen readers will not be accessible to these users. Similarly, text in an image that is then made accessible to screen readers using off-page text will similarly be inaccessible to these users. As such, it is imperative that these users be considered when using ARIA to improve accessibility.
How To Use ARIA
The Rules Of ARIA
Before we dive into our example, we should review the “Rules Of ARIA.” The W3C places these rules immediately after their brief introduction on the Using Aria page, so it seems wise to at least briefly enumerate them here:
xRule #1: Don’t Use ARIA
This is perhaps the most important rule. The basic premise is this: If you can achieve the desired accessible result using the native, built-in HTML API, prefer this over using ARIA to wire in accessibility after you’ve written inaccessible markup.
Rule #2: Don’t Change Native Semantics
This rule basically exists to tell you not to start changing native built-in accessibility with ARIA — this way lies madness. For instance, don’t try to take an anchor (which has a native role of
link) and try to make it into a heading by giving it
role="heading". Instead, choose generic wrappers without built-in accessibility, like a
<span>, to augment with ARIA (or, even better, just wrap the anchor in a
<h6> and don’t use ARIA at all — see Rule #1).
(For what it is worth, the rule also specifies some circumstances under which it might be acceptable to do this.)
Rule #3: If It’s Good Enough for the Mouse, It’s Good Enough for the Keyboard
Another critical rule: if you have an interactive element, it must be interactive by keyboard. This is intended to prevent developers from creating components that are interactive when clicked or hovered with the mouse but are inaccessible for a keyboard user. Frequently the W3C accessible widget design patterns document will include requirements/instructions for proper keyboard accessibility, so look there to get started.
Rule #4: Don’t Hide Focusable Elements from the Accessibility Tree
If an element is focusable by the keyboard, then it should not be hidden from the accessibility tree. This seems super obvious when you think about it; it’s like having a rule that says “the elevator door should always open when it stops on a floor to drop someone off” or “a light switch should do something when you flip it.” But apparently it was a frequent and/or compelling enough issue that the W3C made it a rule of ARIA.
Rule #5: Interactive Elements Must Have an Accessible Name
Marked as a “work in progress” as of this article’s release, this rule states that an accessible name is required for any interactive element. For native HTML, this is generally achieved using a
<label> element wired to a control via wrapping or by leveraging the
for attribute. However, a
<label> element will not automatically provide a name to a non-labelable element, even if it is given a
role that corresponds to a labelable element. Instead, the
aria-labelledby attributes should be used.
ARIA Example Implementation
ARIA has several accessible roles that can be applied to elements in an HTML document. While roles are not necessarily the most fundamental component of ARIA accessibility tagging, they provide us with a nice jumping-off point for creating widgets that leverage several concepts in the ARIA API as well as with several considerations of how to use ARIA judiciously. Furthermore, the W3C provides an ARIA Authoring Practices document that includes descriptions and requirements for complete role implementations as well as working example implementations. For this reason, we’ll be using two separate roles to build example components.
A checkbox is a good starter example because it nicely illustrates the cost in effort for creating custom versions of native controls. For contrast, let’s take a look at a native implementation of a checkbox:
That’s it. Easy, right? Here it is working in a https://codesandbox.io/:
It requires very little code, and it’s accessible and keyboard navigable right out of the box.
SO — how would we build our own custom implementation that we could make properly accessible using ARIA?
The first thing I always do when I need to implement some custom control in which I’ll need to manually wire in the accessible bits is to check the WAI Best Practices: Design Patterns And Widgets document to see if the control I am building has a predefined design pattern I can work from. As expected, this document contains guidelines for creating an accessible custom checkbox. Even better, most of these design patterns also include an example page that can be used to further understand the implementation and to check your own implementation against to compare and make sure you have captured all the necessary requirements; this is the accessible custom checkbox example page.
Let’s first look at what our naïve, inaccessible version of a custom checkbox might look like:
This is already a lot more code! What we have here is a series of generic
<span> containers that accomplish the work of the label, checkbox, and checkmark. We listen on the
click of the fake checkbox itself, and toggle a
.hide class on the fake checkmark. You can see this in action below:
This works, but it has several problems. Before we even get to the accessibility issue, we are using the DOM to maintain state based on the presence or absence of an arbitrary
.hide class. This is flimsy; we would be better off maintaining some separate data-model in JS, or at least using a data attribute if we intend on using the DOM as the source of truth for state.
The next problem is that there is zero accessibility beyond those users who see the screen and use a mouse or touchpad. There is no way to focus our custom checkbox or check and uncheck it with the keyboard. There is no accessible name, which we can see by opening the dev tools and inspecting it. Clicking the fake label doesn’t actually toggle the checkbox, something that works out of the box with our native implementation. There is no role to indicate to a screen reader that this is a checkbox, nor any accessible indication of the checked/unchecked state.
Luckily for us, ARIA provides us the API to accomplish our goals as well as some excellent documentation as to how to go about doing so.
If we visit the WAI ARIA Authoring Practices Design Patterns doc for checkboxes we can track down all the requirements needed to wire this up properly in ARIA:
- The ability to focus with the keyboard and change the state with the spacebar
- An accessible label
- Checked state managed with
- Additionally grouping and labelling under certain conditions
Let’s take a look at an implementation now that meets all these requirements:
So what have we changed from our initial implementation in order to meet the requirements defined by WAI?
- We have added an additional handler on the
keydownevent that is sniffing for the spacebar to determine whether or not to run the handler function
- We have expanded the click handler to the label
- We have made our custom checkbox focusable by setting
tabindex="0", and ensured that the focus indicator would be visible
- We have given the custom checkbox a
- We have wired up an accessible name by pointing the
aria-labelledbyattribute at our fake label
- Rather than toggling an arbitrary class, we now toggle the
aria-checkedattribute, and use CSS to hide/show the checkmark on the basis of this attribute
- We have hidden the actual checkmark from the accessibility tree, as the checked state is properly tracked in ARIA and reading it would only be confusing
You can see this implementation, next to the native implementation, in the sandbox below:
As you can see, this is a lot more code than the native checkbox to achieve the same effect. In most cases, it would probably be preferable to use a solely CSS-based approach to customize a checkbox than attempt to recreate one entirely using JS and ARIA.
Tabbed Container Example
A tabbed container is a better use-case for ARIA, as it represents UI/UX that isn’t natively available in HTML.
A tabbed container is a widget with several “tabs” along the top that correlate to some topic to be shown in the “main” area below (or to the side). When a “tab” is activated, the associated content area shows the content correlating with that tab — usually some kind of visual treatment is given to the currently active tab to differentiate it from the other tabs.
As before, let’s start with an inaccessible example of what this widget would look like:
Here is a working example:
Just as before, this works just fine for a user who is looking at the screen and mostly navigating by mouse. However, a user using the keyboard and/or a screen reader would have a very different experience. First and foremost, the interactive controls — the tabs — are not focusable, and thus cannot be accessed or controlled via the keyboard.
However, this is not the only issue here. There is also the fact that the inactive pane content is still rendered and accessible in the DOM; the active content is given a higher
z-index, but the inactive content is still beneath it. As such, a screen reader would traverse all the tab names, then all the content in order. Or, if a keyboard user was tabbing through focusable elements, the active element could be a link or button in a pane that isn’t visible on the page. This, obviously, represents a bad user experience.
So how do we fix it? As before, our first stop is the “tabs” section in the WAI ARIA Design Patterns docs and the linked example implementation of an accessible tabbed container. There we find the various ARIA tagging needed:
- A container of
role="tablist"that contains all the tabs (with
role="tab") with an accessible label
- The content for each tab is in a container marked as
role="tabpanel", and the relation to the tab is wired via
- Some additional tagging where applicable
Furthermore, there are lengthy and specific keyboard controls designated for navigating tab content:
tabkeypress focuses into the active tab in the
tablist, an additional
tabkeypress will focus into the next focusable element
- When focused on a
tablist, arrow keys (and optionally the
endkeys) can navigate the tabs; if the content loads quickly without performance implications or delay (for an AJAX request, for instance) then the newly focused tab can and should be automatically activated
- Additional keyboard navigation rules for specific applications
With these in mind, we can start reworking our inaccessible tabbed container example to be accessible.
The first thing we will focus on is wiring up the proper ARIA tagging — assigning roles and wiring relationships:
So what have we done? We have:
- Wrapped the tabs in a
role="tab"to each tab
- Given each content pane
- Associated each tab with its pane using
aria-selectedto indicate the active pane in the tabbed container
- Reused quite a bit of CSS by simply selecting by ARIA attributes
- Updated our styling to give all non-active tab panels
display: none, so that they cannot be accidentally accessed via screen reader or keyboard focus when not visible
We can now look at the accessibility tree in our dev tools and see that all the wiring in our implementation matches the example.
A few key places I deviated from the WAI example:
- The WAI example uses
role="tab"for the tabs, whereas I used
divelements. I imagine the reasoning they opted for this route was it gave them some keyboard focus for free; however, it would seem to me to violate the second rule of ARIA, which states that you should never reassign a
roleusing ARIA, so I opted to use a role-less element instead. It is possible, however, that they made this decision because the button represented a “fallback role,” which the second rule deems as an acceptable instance of
rolereassignment. You might have to use your own judgement in some of these cases.
- The WAI example leverages
hiddento hide elements; however, as
display: noneshould be sufficient for both the visible UI as well as preventing content from being in the accessibility tree, I opted for this approach.
Now that our accessible states and relationships are wired up, we have to add keyboard access to meet the requirements in the design pattern doc and adhere to the third rule of ARIA. Because all of our panel content loads quickly without performance implications, we will adhere to the recommendation of loading the content automatically as the user navigates through tabs. We’ll do some careful additions of
tabindex attributes and add the following code to drive keyboard controls:
We’ve abstracted the code that actually focuses a tab into its own function that we call from the
click handler. Then, we’ve added a
keydown handler that sniffs on whether the pressed key was an arrow key, then interrogates the current focused tab and figures out what the next focused tab should be, including checks to allow the focus to wrap when navigating past the end of the tabs. Beyond that we’ve added some additional handling around
tabindex. I also did some adjustments to the focus
outline for visibility in this example; please note that this is often considered an accessibility antipattern.
You can see the working example below:
While this is a lot of code, it provides us with an interactive page element that includes full accessibility like a native element might provide. We did it!
If the ARIA role or aria-* attribute does not rely on scripting to provide interaction behavior, then it is safe to include the ARIA markup inline. For example, it is fine to add ARIA landmark roles or ARIA labeling and describing attributes inline.
Otherwise insert, change and remove ARIA via scripting.
Take a look at the following example — it starts with the tab/pane content in
dd elements inside a
Tools and References
As we discussed *way* back at the start of this article, two of the most important things when learning a new technical skill are:
- Knowing where the best reference materials are (and how to navigate them), and
- Knowing the appropriate tools for creation, debugging, etc.
ARIA is no exception. After a fair amount of futzing and fumbling when I first started learning ARIA, I’ve settled on the materials and tools that best enable me to get things done quickly.
For reference material:
- The W3C WAI ARIA Practices Design Patterns and Widgets — This is my first click when trying to figure out how to build something with ARIA; I see if a similar interface is listed in this document, then read the requirements and, critically, review their example implementation, which can then be used as a reference/comparison for my own implementation.
- MDN — A bit obvious, but for quick, human-readable references on specific ARIA attributes, as well as a collection of links back to the spec, MDN is hard to beat.
- The dev tools in both Chrome and Firefox have excellent tooling for interrogating the accessibility tree; I will often interrogate the components of an existing WAI example implementation and then my own implementation and make sure that I haven’t missed anything.
- Both Chrome and Firefox also have automated accessibility audits built into the developer tools. These are not a complete, holistic replacement for proper review and testing by a knowledgeable and discerning eye, but they can be a decent initial check for obvious problems that can be identified programmatically.
- Getting familiar with one or more screen readers and using them to navigate your document is probably the closest you can get to verifying you’ve set up your ARIA accessibility properly without either being a screen reader user yourself or user testing with users who primarily use a screen reader.
- The truly excellent Testing-Library, created by Kent C. Dodds, is a testing library that strives to enable developers to write UI tests that mimic the way a user would interact with the site, with an emphasis on querying for UI with accessible queries that “reflect the experience of visual/mouse users as well as those that use assistive technology.” Adopting a library such as this can help you catch accessibility issues you may have missed during development.
You have probably not become an expert in ARIA by reading this article. Since the author (me) is not an expert, it was unlikely there was any way that that would happen. However, you have hopefully gained an understanding of what ARIA can do, what references to use when building for ARIA, and how to understand how ARIA is impacting the accessibility of your site.
Remember that, while ARIA is an awesome tool, it is not a magic cure-all; it cannot resolve all your accessibility problems. Accessibility is many things, and ARIA is really intended for a narrow set of concerns: screen readers and keyboard-only users (and perhaps some other assistive technologies).
Try to get in the habit of considering accessibility as you develop (or even, better, before you start developing). Think about how your page might be able to be used by a person who only uses the keyboard, or can’t see the screen. Use the accessibility dev tools, test with a library like Testing Library that emphasizes accessibility, and try to get acquainted with navigating with the keyboard and a screen reader. Get in the habit of checking ARIA references for how to implement a feature; remember, in addition to providing attributes for roles, states, and properties, ARIA can also provide functionality like updating a user to changes or notifications with ARIA live regions.
If you have made it this far, thank you for reading this egregiously long article. If you enjoyed this read and would like to learn more about accessibility, consider starting at the beginning of this series. I hope you can take what you have learned here and begin to apply it to your work. Good luck, and happy coding!
- Photo: “Baguettes, Paris, France — panoramio”
Nick Thweatt, CC BY-SA 3.0 <https://creativecommons.org/licenses/by-sa/3.0>, via Wikimedia Commons
Nathan Smith, CC BY 2.0 <https://creativecommons.org/licenses/by/2.0/>, via Flicker
- Photo: “Lego Opera Singer”
Ted Drake, CC BY-ND 2.0
<https://creativecommons.org/licenses/by-nd/2.0/>, via Flicker
- Photo: “SpiderMan | Papertoy”
cristian.villazón.valencia, CC BY-NC-SA 2.0 <https://creativecommons.org/licenses/by-nc-sa/2.0/>, via Flicker
- Photo: “tree, wood, chain, food, produce, firewood, baking, bread, saw, cut, baguette, tribe, risk, chainsaw, dangerous, baked goods, white bread, woodworks, risk of injury, snack food, chainring, stihl”
Photographer Unknown, CC0 1.0
<https://creativecommons.org/publicdomain/zero/1.0/>, via PxHere