# The accessibility tree and ARIA

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

Concept · The browser · https://levelcode.org/concepts/accessibility-tree

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.
