ESC

Type to search the knowledge base.

Accessible Names Computation

How browsers compute accessible names from content, labels, aria-label, and labelledby — and how that powers Testing Library queries.

advanced3 min read
  • accessibility
  • accessible-names

Every interactive control needs an accessible name — the string assistive tech uses to announce it (“Button, Submit”). Browsers compute names from a priority of sources defined by the Accessible Name and Description Computation spec. If the name is wrong or empty, users and getByRole(..., { name }) both fail.

Docs: AccName, MDN Using ARIA.

Mental model (simplified priority)

For many controls, naming looks roughly like:

  1. aria-labelledby (referenced elements’ text)
  2. aria-label
  3. Associated <label>
  4. value / content (e.g. button text, alt on img-in-button)
  5. title (last resort, inconsistent)

Exact algorithm is nuanced — when debugging, use the accessibility tree in DevTools.

Good patterns

<label for="q">Search</label>
<input id="q" type="search" />
<button type="submit">Create project</button>
<button type="button" aria-label="Close">
  <svg aria-hidden="true">…</svg>
</button>

aria-labelledby composition

<h2 id="billing-title">Billing</h2>
<button type="button" aria-labelledby="billing-title edit-label">
  <span id="edit-label">Edit</span>
</button>
<!-- Name ~ "Billing Edit" -->

Order of ids in aria-labelledby matters.

Description vs name

  • Name — primary identity (aria-label, label)
  • Description — extra help (aria-describedby, sometimes title)
<input id="pw" aria-describedby="pw-hint" />
<p id="pw-hint">At least 8 characters.</p>

SR may announce description after name/role.

Debugging

Chrome/Firefox/Safari: Accessibility pane shows computed Name / Role / Description. If name is empty, fix markup before writing a test id.

// Fails if button has no name
screen.getByRole('button', { name: /save/i });

Footguns

  1. Icon button with no aria-label / text.
  2. Multiple labels competing.
  3. aria-label overriding useful visible text unintentionally.
  4. CSS content for critical names (unreliable).
  5. SVG without title/host name when meaningful.

Interview out-loud answer

“Accessible name is computed from labelledby, aria-label, native label, then content. I verify in the a11y tree and prefer visible labels. Testing Library name options use the same computation, so good names make tests and AT work together.”

Name from content examples

<a href="/docs">Documentation</a>
<!-- name: Documentation -->

<button type="button"><img src="trash.svg" alt="Delete" /></button>
<!-- name often from img alt when button has no other text -->

<button type="button"><img src="trash.svg" alt="" /> Delete</button>
<!-- name: Delete — decorative img alt empty -->

For buttons, visible text is the most robust name source. Nested image alt participates in name calculation and can surprise you when both alt and text exist.

Labelledby vs label string

aria-labelledby wins over aria-label in the computation when both are present. Prefer one clear mechanism. Debugging “wrong name”: remove extra ARIA until the tree shows the visible text you expect, then add only what’s missing.

Descriptions are not names

<button aria-describedby="tip">Export</button>
<p id="tip">Downloads a CSV of the current filters.</p>

SR may announce the description after the name — order varies. Don’t put the only critical identity in the description.

QA workflow

Open the Accessibility pane, read Name, assert with getByRole in tests, and spot-check design-system primitives once with VoiceOver or NVDA.

Platform differences

VoiceOver, NVDA, and TalkBack may announce the same tree node with slightly different wording. Optimize for correct roles, names, and states in the accessibility tree rather than chasing identical speech strings. If Chrome’s a11y pane shows the right name but one SR is quiet, check focus mode vs browse mode and whether the control is actually focused.

Further reading

Related guides