aria-label and aria-labelledby provide accessible names when visible text alone doesn't suffice. They're powerful and frequently misused. Misuse breaks accessibility worse than no label at all: redundant aria-label overrides visible text, broken aria-labelledby references produce silent failures, aria-label on non-interactive elements is invisible. This guide walks through the right patterns and the common errors.
grep -rE "aria-label" src/templates/Most existing aria-labels in templates were either added correctly (icon button) or added wrongly (decorative duplicate of visible text). Each needs review.
<button aria-label="Close dialog"> <svg><!-- x icon --></svg> </button>
<a href="/pricing.html" aria-label="View cart, 3 items"> <svg><!-- cart icon --></svg> <span>3</span> </a>
<h3>Article about pricing</h3> <a href="/pricing" aria-label="Read more about pricing">Read more</a>
Without aria-label, multiple "Read more" links on the same page all announce identically. aria-label disambiguates per link.
<input type="search"
aria-label="Search products"
placeholder="Search...">
<nav aria-label="Main">...</nav> <nav aria-label="Footer">...</nav> <nav aria-label="Breadcrumb">...</nav>
Without distinguishing labels, screen readers announce all of them as just "navigation".
<!-- WRONG --> <button aria-label="Submit">Submit</button> <!-- RIGHT --> <button>Submit</button>
Pure noise. The visible text already provides the accessible name.
<!-- WRONG --> <button aria-label="Submit form">Submit</button> <!-- RIGHT — pick one --> <button>Submit form</button>
Sighted users see "Submit". Screen-reader users hear "Submit form". They're not getting the same UI.
<!-- WRONG (ignored by most screen readers) --> <div aria-label="Important warning">Read this</div> <!-- RIGHT (use semantic HTML) --> <div role="alert">Read this</div> <!-- BETTER (no ARIA needed) --> <p><strong>Important warning:</strong> Read this</p>
<!-- WRONG — abuse --> <a href="/" aria-label="Acme Co home page best products lowest prices"> <img src="/logo.svg" alt=""> </a> <!-- RIGHT --> <a href="/" aria-label="Acme Co home"> <img src="/logo.svg" alt=""> </a>
<!-- WRONG --> <label for="email">Email</label> <input id="email" aria-label="Your email address"> <!-- Screen reader announces "Your email address", hiding the visible label --> <!-- RIGHT --> <label for="email">Email</label> <input id="email">
<section aria-labelledby="related-heading"> <h2 id="related-heading">Related products</h2> <ul>...</ul> </section>
Screen reader announces "Related products, region" when entering the section.
<span id="price-label">Maximum price</span>
<input type="number"
aria-labelledby="price-label">
<span id="amount">100</span> <span id="currency">USD</span> <button aria-labelledby="amount currency">Confirm</button> <!-- Announces: "100 USD, button" -->
# In a built page, get all referenced ids and verify they exist
grep -oE 'aria-labelledby="[^"]+"' output.html | \
awk -F'"' '{print $2}' | tr ' ' '\n' | sort -u > referenced
grep -oE 'id="[^"]+"' output.html | \
awk -F'"' '{print $2}' | sort -u > existing
diff referenced existing | grep '^<'
Anything in referenced but not in existing is a broken aria-labelledby reference.
Tab through interactive elements with VoiceOver or NVDA. Every element should announce:
If an element announces just its role with no name ("button" alone), that's a missing label. If announced name conflicts with visible text, that's a bug.