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:
aria-labelledby— the text of other elements, byid.aria-label— a string attribute.- The native label:
labelfor form controls,altfor images,captionfor tables,legendfor fieldsets. - The element's text content — for buttons, links, and headings.
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-labelonly works on elements that can have a name — interactive elements, landmarks, and a few others. On a plaindivorspan, 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.