Topic 02

Semantics First, ARIA Last

AccessibilityHTML

A screen reader never looks at your page. It cannot see colours, positions or icons. Instead it reads a second structure the browser builds from your HTML, called the accessibility tree: a simplified copy of the page that keeps only what a person needs to know about each part, such as "this is a link called Projects".

A museum audio guide is a fair picture of it. The guide describes each exhibit from its label. An unlabelled exhibit is silence, and a wrong label is worse than silence, because the visitor believes it. The accessibility tree works the same way, with one difference: the labels come from your HTML, so you are the one who writes them.

Real HTML elements fill the tree correctly on their own. ARIA, short for Accessible Rich Internet Applications, is a set of attributes for changing the tree by hand when no element fits. It is useful in a few places and harmful in many more, and this page is about telling the two apart.

What Is in the Accessibility Tree?

Every node in the tree carries three kinds of information. Its role says what kind of thing it is: a button, a link, a heading, a list, a navigation landmark. Its name is what it is called: the button "Send", the link "Projects". Its state is anything that can change: whether a checkbox is checked, a panel expanded, a button pressed.

A screen reader turns those three into speech. Tab to the contact form's send button and a screen reader says something like "Send, button": the name, then the role. Tab to a filter button that is switched on and it adds the state, "pressed".

You can see the tree in Chrome DevTools. In the Elements panel, select an element, then open the Accessibility tab in the pane beside Styles. It shows the element's place in the accessibility tree and, under Computed Properties, its name and role. The pane also has a switch that replaces the Elements view with the whole accessibility tree, which is a quick way to see a page the way a screen reader does.

One header, two trees
The DOM: what the browser draws
A header element with a link to Home, then a nav element holding a list of three links, each styled with classes and colours.
The accessibility tree: what a screen reader reads
banner; link "Your Name"; navigation; list of 3 items; link "Home", link "Projects", link "Contact".

Where Does a Name Come From?

The browser works out each name from the HTML, and it looks in a fixed set of places. A link or a button takes its name from the text inside it. An image takes its name from its alt text. A form field takes its name from its label. That is why the book has asked for all three since the start.

The trouble starts with a button that shows only an icon, such as a menu button drawn as three lines. The icon image has an empty alt, because it is decoration, so the button contains no text at all. Its name is empty, and a screen reader says only "button". A sighted user sees what it does; a blind user has to guess.

Fragment
<!-- No name: a screen reader says only "button" -->
<button type="button">
  <img src="images/menu.svg" alt="">
</button>

<!-- Named: "Menu, button" -->
<button type="button" aria-label="Menu">
  <img src="images/menu.svg" alt="">
</button>

Both buttons look identical on screen: the same three-line icon. Select the first in DevTools and the Accessibility pane shows its role as button with an empty name. Select the second and the name is "Menu", taken from the aria-label attribute, which gives an element a name without changing anything you can see. Giving the image an alt of "Menu" would work too, because the button would then take its name from the image inside it.

What Is the First Rule of ARIA?

A W3C guide for authors, Using ARIA, calls this the first rule of ARIA: if a native HTML element already does the job, use it instead of adding ARIA to something else. The ARIA standard itself says the same thing in other words: its roles are meant for when no HTML element with that meaning exists. A button is a button. A nav is a navigation landmark. An h2 is a heading. None of them needs a single ARIA attribute.

The reason is that ARIA changes only what the tree says, never what the element does. Chapter 9 showed a clickable div that the keyboard could not reach. Giving it the button role, with role="button", makes a screen reader announce "button". That is a promise, and the promise is false.

Fragment
<!-- Announced as a button, but Tab skips it and Enter does nothing -->
<div role="button" class="save">Save</div>

<!-- A real button: focusable, and Enter and Space both press it -->
<button type="button" class="save">Save</button>

On screen the two can be styled to look the same. Press Tab through the page and focus lands on the real button but jumps straight past the div. Even if a screen-reader user finds the div another way and hears "Save, button", pressing Enter does nothing, because nothing in the page tells it to. Making the div behave properly would take a tabindex, a keyboard listener and more; the real button has all of it built in.

Where Is ARIA the Right Tool?

ARIA earns its place when HTML has no element or attribute for what you need to say, usually a state that your JavaScript changes. The book has already used every case you are likely to need, each in the chapter where it arose.

The aria-expanded attribute told a screen reader whether the show-and-hide panel in Chapter 9 was open. The aria-pressed attribute told it which of the project filter buttons was switched on. In the contact form of Chapter 10, aria-describedby linked each field to its error message, and aria-invalid marked the field as wrong. And the form's status area used role="status", which makes a screen reader announce new text in it without moving focus; aria-live is the general attribute behind that behaviour.

Notice what all of them have in common. Each one sits on a real element, a button, an input, the status area, and adds a piece of information that element cannot express by itself. None of them replaces an element.

How Do You Hide Text from Sight but Not from Sound?

Sometimes a sighted user can tell what something is from its position or an icon, and a screen-reader user needs a few words instead. The usual answer is a visually hidden class: text that stays in the accessibility tree but takes up no visible space. Chapter 4 named it; here it is in full.

css/style.css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

The rule shrinks the element to a single pixel, clips away even that, and lifts it out of the layout so nothing around it moves. The text is still in the page, so a screen reader reads it. You cannot use display: none for this: as Chapter 4 showed, it removes the element from the accessibility tree as well as from the screen.

Fragment
<button type="button">
  <img src="images/menu.svg" alt="">
  <span class="visually-hidden">Menu</span>
</button>

This is the menu button again, named a second way. The screen shows only the icon; a screen reader says "Menu, button"; and DevTools shows the name "Menu", taken from the hidden text inside the button. Many developers prefer this to aria-label, because the words live in the page's text, where translation tools find them too.

Common Confusions
  • "More ARIA makes a page more accessible." Wrong or unneeded ARIA makes it worse. WebAIM's 2026 survey of a million home pages found that pages using ARIA averaged about 59 detected errors, against 42 on pages without it.
  • "aria-label changes what is shown." It changes only what is announced. The screen looks exactly the same.
  • "Giving a div the button role makes it a button." It makes a screen reader say "button". Focus, Enter and Space are still missing until you add them yourself, or use a real button.
Why It Matters
  • Knowing the tree explains almost every screen-reader bug: something has the wrong role, no name, or a state nobody updates.
  • The rule "native element first" prevents most of those bugs before they exist.

Knowledge Check

Which three things does each node in the accessibility tree carry?

  • A tag name, a class and an id
  • A role, a name and a state
  • A colour, a size and a position
  • A file, a line number and a style

Which use of ARIA follows the advice on this page?

  • role="button" on a div used as a button
  • role="heading" on a div used as a title
  • aria-pressed on a real filter button
  • aria-label on a paragraph to reword it

A button contains only the text "Send". Where does its accessible name come from?

  • From the class name used in the stylesheet
  • From the title of the page it appears on
  • From the id attribute that the script uses
  • From the text between its two tags

You add aria-label="Close" to a button that shows only a cross icon. What changes?

  • What a screen reader announces for it
  • The word Close is drawn beside the icon
  • The button now closes things when clicked
  • A tooltip saying Close appears on hover

You got correct