Skip to content
AgileLogics
6 min read

Accessible Names and Descriptions

Prerequisites: Accessibility basics

Browsers build an accessibility tree alongside the DOM. For each interactive element, assistive technology needs at least:

  • a role — "button", "link", "checkbox", "heading level 2"...
  • an accessible name — what it's called: "Add to cart".
  • its state — checked, expanded, disabled, invalid...
  • optionally a description — extra help text.

Browser developer tools show all of this in the Accessibility panel.

Where names come from

In order of precedence, roughly:

  1. aria-labelledby — the text of other elements, by id.
  2. aria-label — a string attribute.
  3. The native label: label for form controls, alt for images, caption for tables, legend for fieldsets.
  4. The element's text content — for buttons, links, and headings.
  5. title — a last resort (not shown on touch screens, not reliable).
<button>Add to cart</button>

<button aria-label="Delete item">🗑</button>

<h2 id="billing-title">Billing address</h2>
<form aria-labelledby="billing-title" action="/billing" method="post">
  <label for="street">Street</label>
  <input id="street" name="street">
</form>

<a href="/report.pdf"><img src="pdf-icon.svg" alt="" width="16" height="16"> Annual report (PDF)</a>
  • The first button's name is its text.
  • The emoji button's name is "Delete item", not "wastebasket".
  • The form (a landmark when it has a name) is called "Billing address".
  • The link's name comes from its text; the icon is decorative (alt="").

Checking a name from JavaScript

There's no standard DOM method to read the computed accessible name, but you can see what the label wiring is doing. labels lists the label elements attached to a control:

<label for="city">City</label>
<input id="city" name="city">
<label>Postcode <input id="zip" name="zip"></label>
<input id="phone" name="phone" aria-label="Phone">
<script>
  for (const id of ["city", "zip", "phone"]) {
    const el = document.getElementById(id);
    const text = [...el.labels].map(l => l.textContent.trim()).join(", ");
    console.log(id, el.labels.length, JSON.stringify(text));
  }
</script>
city 1 "City"
zip 1 "Postcode"
phone 0 ""

The phone field has no label element — its name comes from aria-label instead, which labels doesn't show. The Accessibility panel in DevTools shows the final computed name for any element.

Rules for good names

  • Visible label = accessible name. If a button shows "Search", don't give it aria-label="Find products" — voice-control users say what they see ("click Search"). The name should at least start with the visible text.
  • Keep names short; put extra detail in a description (aria-describedby).
  • Don't include the role: "Close", not "Close button".
  • aria-label only works on elements that can have a name — interactive elements, landmarks, and a few others. On a plain div or span, it's ignored by many screen readers.

Practice

Give this icon-only search button an accessible name and connect a description explaining that search covers recipes only.

<!-- Wrong: no accessible name -->
<button><img src="magnifier.svg" alt="" width="20" height="20"></button>
Show solution
<button aria-label="Search" aria-describedby="search-scope">
  <img src="magnifier.svg" alt="" width="20" height="20">
</button>
<p id="search-scope">Searches recipes only.</p>

Setting alt="Search" on the image (and no aria-label) would work just as well — the button's name comes from its content.

Related developer tools