Concept · The browser

The accessibility tree and ARIA

What a screen reader actually sees: roles, names and states — and why native elements beat ARIA.

3 minARIAscreen readersemantic HTMLaccessible name

Alongside the DOM, the browser builds an accessibility tree: a simplified tree that screen readers, voice control and other assistive technology read instead of your pixels. Each node has:

  • a role: button, link, heading (level 2), checkbox, textbox, navigation…
  • an accessible name: what it's called ("Delete invoice", "Search");
  • states and properties: checked, expanded, disabled, invalid, current page…

A screen reader announces those three things: "Delete invoice, button." If they're missing or wrong, that user gets a broken page, even when it looks perfect.

Native HTML fills it in for you

html
<!-- Role: button. Name: "Save". Focusable, Enter/Space activate it. -->
<button type="button">Save</button>

<!-- Role: none. Name: none. Not focusable. Keyboard does nothing. -->
<div class="btn" onclick="save()">Save</div>

Semantic elements like <button>, <a href>, <label>, <nav>, <main>, <h1>–<h6>, <ul> and <dialog> give you correct roles, names and keyboard behaviour for free.

Where the name comes from

In order of precedence: aria-labelledby, then aria-label, then native labelling (<label for>, alt, <caption>), then text content, then title. Common bugs:

  • An icon-only button with no name. Add aria-label="Close" or visually hidden text.
  • An input with only a placeholder. Placeholders aren't reliable labels, so use a <label>.
  • A link whose name is "click here." Names should make sense read out of context.

ARIA: a repair kit

ARIA attributes change what the accessibility tree says without changing behaviour. role="button" on a <div> makes a screen reader announce a button, but the div still can't be focused or activated with the keyboard until you add tabindex="0" and key handlers yourself.

The first rule of ARIA: if a native element does it, use that instead. Reach for ARIA when HTML has no equivalent:

  • aria-expanded, aria-controls for disclosure widgets and menus;
  • aria-live="polite" to announce updates like "3 results found";
  • aria-current="page" on the active nav link;
  • aria-hidden="true" on decorative icons.

Wrong ARIA is worse than none. A role that doesn't match the element's behaviour misleads people who trust it.

Checking it

Open the Accessibility panel in your browser's dev tools to see the tree. Testing Library's getByRole('button', { name: 'Save' }) queries the same tree, so tests that use it double as accessibility checks.

Learn it in depth