HTML — One Tag Stands In For a Feature
The Accessibility Tree Is a Different Document
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.
- If there is a usable HTML element, use it. Use ARIA only when there is no matching element
- Don't change native semantics.
<h2 role="tab">erases that item from the list of headings - Every interactive ARIA widget must be operable with the keyboard
- Don't use
role="presentation"oraria-hidden="true"on elements that can receive focus - 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.
role="alert"— read it right now. Use it for errorsaria-live="polite"— read it when what is being read finishes. Use it for things like the result countrole="status"— a role with the same meaning as polite
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.