TT Lab
Get started
Learn Learning paths Courses

HTML — One Tag Stands In For a Feature

The Accessibility Tree Is a Different Document

Continue in TT Lab

In one line

The browser builds an accessibility tree separately from the DOM. What a screen reader reads is not the screen but that tree, and ARIA fixes only that tree — it changes no behavior at all.

Why this was needed

When they get a finding to fix accessibility, people usually attach role and aria-*. But the result is often worse than before. In fact, surveys repeatedly find that pages that use a lot of ARIA have a higher error rate than pages that use none.

The reason is simple. ARIA is a promise, not an implementation. If you attach role="button", you have told assistive technology "this is a button," and from that moment on, making it behave like a button is entirely the developer's job. If you only say so and don't implement it, a screen reader user is told it is a button, presses it, and meets a screen where nothing happens. That is worse than saying nothing.

How it works

When the accessibility tree is built from the DOM, four things are determined for each element.

Item What it is Example
role What this is button · link · heading · list
name What it is called "Save"
state What state it is in now pressed · expanded · disabled
value What value it holds 40 on a slider

Native HTML elements are born with these four already. A <button> has the role button, the text inside becomes the name, disabled becomes the state, and focus and Enter and Space handling are built into the browser.

A <div role="button"> gets only the role of these. The other three, and the behavior, all have to be built by hand. MDN's accessibility tree explanation and ARIA in HTML sum up this relationship.

Five rules

The WAI-ARIA usage rules boil down to five lines. They matter in this order.

  1. If there is a usable HTML element, use it. Use ARIA only when there is no matching element
  2. Don't change native semantics. <h2 role="tab"> erases that item from the list of headings
  3. Every interactive ARIA widget must be operable with the keyboard
  4. Don't use role="presentation" or aria-hidden="true" on elements that can receive focus
  5. Every interactive element must have an accessible name

The second rule causes accidents most quietly. A role is not something you add but something that overwrites. If you use <ul role="presentation">, that list is no longer a list, and a screen reader doesn't say "five items." It is actually common to attach it to remove bullets for design reasons and end up erasing the whole structure.

State is easy to lie about

State attributes such as aria-expanded, aria-selected, aria-checked, and aria-current must be updated by hand. CSS that rotates an arrow icon updates automatically, but these attributes don't.

So this is an accident that happens often — the panel is open, but aria-expanded is still false. To someone looking with their eyes it is an open screen, and to a screen reader user it is a closed screen. One of the two is lying, and the one that lies is always the assistive technology side.

aria-controls, aria-labelledby, and aria-describedby point to other elements by id. If that id doesn't exist, the browser raises no error and silently ignores it. References often break when components are moved or rendered conditionally, and since nothing changes on screen, no one knows.

Live regions

The number of search results, a save-complete message, an error message. Text that appears later isn't read on its own. This is because the screen reader is already reading something else.

There is one thing to watch out for. A live region must already be in the DOM. If you create the element from scratch and attach role="alert", it often isn't read. It is safer to put an empty container first and then change the text inside it.

What it looks like in the field

Three things come up repeatedly.

First, an icon button has no name. <button><svg/></button> has no name, so it is read only as "button." One line of aria-label="닫기" (the Korean word for "close") fixes it, yet it is the thing most often left out.

Second, decorative icons aren't hidden. An icon drawn with a text character (★, ▶) is read out as is, producing sounds like "black star favorites." Attaching aria-hidden="true" quiets it.

Third, the design system overuses role. A role used wrongly once spreads through the component to the whole site. You can't stop this by fixing individual pages; you have to catch it at the component level.

There is one thing to remember. Bad ARIA is worse than none. The question to ask before fixing is not "which role should I attach" but "isn't there already an HTML element that does this?"

What to check in the next quiz

You check what the accessibility tree contains, what role overwrites, and in what ways state attributes and live regions break quietly.