The Accessibility Tree
Date: 2026-08-16
A second tree the browser builds alongside the DOM, containing only what assistive technology needs: what each thing is, what it’s called, and what state it’s in. Screen readers read this, not your markup — so a page can look right and expose nothing.
What it is
The accessibility tree is a parallel structure derived from the DOM, where each node carries an accessible name, role, state and value. It’s what the operating system’s accessibility APIs expose, and therefore what screen readers, voice control and switch devices consume.
DOM ACCESSIBILITY TREE
<button id="add"> button
<svg aria-hidden="true">…</svg> name: "Add to basket"
Add to basket role: button
</button> state: enabled, focusable
<div class="btn" onclick="…"> (generic)
Add to basket name: ""
</div> role: generic
state: not focusable
Both look identical on screen. Only the first is usable without sight or a mouse.
The four properties
- Role — what kind of thing it is. Implicit from the element (
<button>→ button), or overridden withrole= - Name — what it’s called. Computed by a specified algorithm with a priority order:
aria-labelledby, thenaria-label, then a<label for>, then the element’s own text content, thentitleas a last resort - State — checked, expanded, disabled, selected, invalid, busy
- Value — the current value of an input, slider or progress bar
Everything assistive technology says about an element comes from those four. If the name is empty, a screen reader announces “button” and nothing else.
Where names go missing
The most common real-world failures, all invisible visually:
<!-- icon-only button: announced as "button" -->
<button><svg>…</svg></button>
<!-- fixed -->
<button aria-label="Close"><svg aria-hidden="true">…</svg></button>
<!-- placeholder is not a label: announced as "edit text" -->
<input type="email" placeholder="Email address">
<!-- fixed -->
<label for="email">Email address</label>
<input id="email" type="email" autocomplete="email">
<!-- link text carrying no meaning out of context -->
<a href="/p/123">Read more</a>
<!-- fixed -->
<a href="/p/123">Read more <span class="visually-hidden">about wool socks</span></a>That last one matters because screen reader users commonly pull up a list of all links to navigate. Twenty entries reading “Read more” is a list with no information in it.
Hiding things correctly
Four ways to hide, and they do different things:
| Method | Visually | From accessibility tree |
|---|---|---|
display: none / hidden | Hidden | Hidden |
visibility: hidden | Hidden | Hidden |
aria-hidden="true" | Visible | Hidden |
.visually-hidden clip class | Hidden | Exposed |
The two on the bottom are the useful pair. aria-hidden is for decoration a screen reader shouldn’t announce — a decorative icon inside a labelled button. The clip class is for text that adds context non-visually.
Never put aria-hidden on anything focusable. You create an element a keyboard can reach and a screen reader can’t describe, which is the worst of both.
Inspecting it
You don’t have to guess. In Chrome DevTools: Elements → Accessibility panel shows the computed name, role, state and the full tree, plus which rule produced the name.
That last part is the useful bit — it tells you why an element is called what it’s called, which turns “the label isn’t working” into a two-second answer.
Where it connects
- Semantic HTML — the cheapest way to get roles and states right is to use the element that already has them
- ARIA — the override mechanism, for genuinely custom widgets only.
role="button"supplies the role and none of the behaviour - Keyboard Navigation — the tree tells you what a thing is; focus order determines whether you can reach it
- Screen Readers — how the tree is actually consumed, which differs by software
- Accessibility Testing — automated tools check a meaningful subset of this and miss most naming and context problems