/ Accessibility Fixes / ARIA Labels

How to Fix ARIA Labels

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.

1. Audit current usage

Step 1
Run the Accessibility audit
Run the Accessibility audit. Filter to ARIA-label findings. Categories typically include: missing accessible name on interactive element, aria-label conflicts with visible text, aria-labelledby references non-existent id, aria-label on element with no role.
Step 2
Inventory existing aria-label across templates
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.

2. When to USE aria-label

Icon-only buttons

<button aria-label="Close dialog">
  <svg><!-- x icon --></svg>
</button>

Icon-only links

<a href="/pricing.html" aria-label="View cart, 3 items">
  <svg><!-- cart icon --></svg>
  <span>3</span>
</a>

Ambiguous link text disambiguation

<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.

Search input without visible label

<input type="search" 
       aria-label="Search products" 
       placeholder="Search...">

Landmark regions of the same type

<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".

3. When NOT to use aria-label

⚠️ Five anti-patterns to remove:

Anti-pattern 1: aria-label duplicates visible text

<!-- WRONG -->
<button aria-label="Submit">Submit</button>

<!-- RIGHT -->
<button>Submit</button>

Pure noise. The visible text already provides the accessible name.

Anti-pattern 2: aria-label conflicts with visible text

<!-- 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.

Anti-pattern 3: aria-label on a non-interactive div

<!-- 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>

Anti-pattern 4: aria-label as keyword stuffing

<!-- 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>

Anti-pattern 5: aria-label hiding a visible label

<!-- 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">

4. Use aria-labelledby correctly

Pattern: section labelled by heading

<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.

Pattern: input labelled by adjacent text

<span id="price-label">Maximum price</span>
<input type="number" 
       aria-labelledby="price-label">

Multiple references

<span id="amount">100</span>
<span id="currency">USD</span>
<button aria-labelledby="amount currency">Confirm</button>
<!-- Announces: "100 USD, button" -->

5. Verify aria-labelledby references

⚠️ aria-labelledby pointing at a non-existent id fails silently. The element gets no accessible name. Always verify referenced ids exist.
Step 1
Find broken references
# 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.

6. Test with a screen reader

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.

💡 The accessible-name algorithm priority: aria-labelledby → aria-label → native label (label element, input title, etc.) → visible text content. ARIA wins when present, so it's powerful and dangerous in equal measure.

♿ Re-run the Accessibility audit

Verify ARIA-label findings are cleared.

Run Accessibility Audit →
Related Guides: Accessibility Fixes  ·  Fix ARIA Errors  ·  Fix Link Text  ·  Accessibility Guide
💬 Got a problem?