The accessibility tree: understanding and debugging
A guided look at the accessibility tree and how it can help you, using my personal experience of developing accessible web features.
Why we need to know about this
In digital accessibility, only a minority of the barriers we could create for our users can be caught through automated tools. Often, especially in development, we are swift to come to the conclusion that our digital experiences are excellent because automated accessibility tools are giving us “full marks”. Automation absolutely has its place, and can be immensely helpful in catching issues. But in the same way that we wouldn’t expect an automated tool or machine to inform us whether or not visual UX is “good”, we shouldn’t expect them to tell us if the semantics, content ordering, and information structure of the page makes sense for users approaching our content using assistive tech.
How do we then approach this? We test! We test with assistive technology users, and throughout development we run screen readers, voice control technologies, and keyboard journeys, among others. But do we actually understand what’s going on under the hood of the interactions between a browser, the HTML and a screen reader — enough to debug when things aren’t working how we expect? Let’s dive into it!
Introducing the accessibility tree
“The semantic HTML structure of a page IS the user experience for assistive technology.”
I was reminded of this quote recently by a colleague of mine, and it’s pretty accurate. Thinking about semantics and structuring early on in the design process helps us to shift accessibility left and prevent the accessible experience from becoming a second-class retrospective patch. But there’s an additional layer sitting between the HTML output and the information that assistive technology consumes from the browser — it’s called the accessibility tree.
The accessibility tree is an API layer that is generated, by the browser, from the DOM. The creation of the tree involves parsing the roles, names and values from the elements we have defined, and putting them into a structure that technology such as a screenreader can interface with and interpret for the end user into announced content. This parsing can be influenced by CSS, and be different depending on the algorithms used.
Some browsers give us the tools to look into the accessibility tree a little more closely — albeit all in slightly different ways.
Inspecting the tree in Firefox
Firefox is currently the web browser that gives us the most access to and best visual representation of the accessibility tree it creates. Let’s take a look at the BBC homepage as an example, and dig into the markup for the global navigation using the element inspector. For the sake of demonstration, I’ll remove some of the parts we don’t need to worry about like wrapping divs and classes.
<nav aria-label="BBC">
<ul role="list">
<li><a href="https://www.bbc.co.uk">Home</a></li>
[...]
</ul>
</nav>
What have we got here, then? Well, at the top level of the global navigation, there’s a navigation element, with an aria-label that has a value of “BBC”. Nested within this is an unordered list element, which contains many list items that have anchor tags within them. For brevity, I’ve truncated and simplified the list items. So what happens when this HTML is parsed into the accessibility tree?
In Firefox, it is possible for me to simply right click on the global navigation and select “Inspect Accessibility Properties”. You can also open the Accessibility tab in the developer tools, which will open the tree at the root — usually “document”.
Now, we can see the elements of the global navigation above as the accessibility tree parses them…
> landmark : "BBC"
> list : ""
> listitem : "Home"
> link : "Home"
text leaf: "Home"
First in the list we have a node of “landmark”. We can take a look into the properties of this node of the tree to the right of the tree itself, by opening the “properties” drop-down. From this, we can pick out the following salient keys:
name: "BBC"
role: "landmark"
> attributes
xml-roles: "navigation"
Ahh — this is the navigation element. We can see that the contents of the aria-label we have provided is now assigned to the name of the node. Using the <nav>
landmark element here is very similar to using the ARIA role=”navigation”
on a non-semantic element such as a <div>
- the role is assigned automatically by using the semantic HTML element. We can see that an additional role of navigation has been assigned in the accessibility tree as a result, under attributes.
A screen reader such as VoiceOver would interpret this tree and might roughly announce (dependent on settings), “BBC navigation, landmark.” The HTML that has been provided, alongside the aria-label, has informed the assistive technology that this node is a navigation landmark, and the unique label assigned to it is “BBC”. Helpfully, this is announced first, so users are given context for what the purpose of the navigation landmark is, and it gives them an idea of what they can expect to be inside it. In this case, the navigation items for the whole BBC!
Let’s dig a little deeper into the tree to the “Home” link, now.
name: "Home"
role: "link"
value: "https://www.bbc.co.uk/"
Alright — we can see here that the name for the link is “Home” — that’s come from the contents of the tag itself. It has a role of link, and a value which is the url in the href. A screen reader might announce this as “Home, link”. The node name gives our assistive tech context again, this time for what the purpose of the link is, so that a user would know upfront where it might take them.
In both cases so far, we’ve seen the name for the node on the tree read out — but what happens if we have an element with both an aria-label and some tag contents?
This snippet is purely for demonstration — I wouldn’t recommend using aria-label in this context, as a text node in the tag is always preferable. But theoretically…
<a href="https://www.bbc.co.uk" aria-label="Go to the homepage">Home</a>
And the accessibility tree properties, once updated:
name: "Go to the homepage"
role: "link"
value: "https://www.bbc.co.uk/"
Oh! The name for the node is now “Go to the homepage”, which is the contents of the aria-label. A screenreader might now announce, “Go to the homepage, link”. By providing an aria-label at the anchor tag level, we’ve actually overwritten the node’s name property, instead of that value being surfaced from the text node child of “Home”.
We have to be careful using ARIA in this way, and it can be helpful to dig into the accessibility tree when we’re hearing announced content that we didn’t expect when testing with different assistive tech. It’s worth mentioning though that different technologies will interpret the tree in different ways, sometimes, so you can’t always rely on the aria-label being the only thing read out. Some tech will interpret both the label and the text node, and some will ignore the label altogether for certain elements. This is why testing across a range of technologies and browsers is super important! If you’d like to dig into the ordering specification to see what should take priority where, you can see that here at W3C.
Why a role of list on the unordered list element?
If you took a close look at that first HTML snippet near the beginning, you may have noticed that even though a <ul>
is being used, an additional role=”list” is being applied. Why is this?
Well, this is a perfect example of the differences between browsers and how they can interpret the HTML differently into their accessibility trees. In Safari, if the stylesheet that controls the styles for the unordered list uses list-style: none
, the browser removes the list role from the accessibility tree node altogether, even though the semantic element is being used.
We can see this by going to the inspector in Safari, and selecting the “node” tab on the rightmost column of information. Down at the bottom there’s an “Accessibility” drop-down. Without the additional role of text being added back in, we see:
role: No matching ARIA role
Where we thought we’d see:
role: list
This is a decision that Safari have made after feedback from assistive technology users — which we can figure out the impact of by inspecting the accessibility tree to check the role. Some pages, where the authors believe that the list semantics are valuable for that feature, reinstate the role, as seen here, which returns the role of list to the node.
Hopefully you now have a basic understanding of the accessibility tree, and the kind of information you can gain for testing and debugging purposes!