Skip to main content
Back to Blog
Developer Guide June 19, 2026

How to Structure ARIA Labels: The Complete Developer Guide

How to Structure ARIA Labels: The Complete Developer Guide

ARIA labels are the most misunderstood part of web accessibility. Developers add them everywhere "just to be safe," or skip them entirely thinking HTML semantics are sufficient. Neither approach is correct. This guide covers what ARIA labels are, when each type is appropriate, and the patterns that actually work for assistive technologies.


What ARIA Labels Are For

ARIA (Accessible Rich Internet Applications) labels provide a text alternative that screen readers announce when a user focuses on an element. They don't change visual appearance — they only affect the accessibility tree that assistive technologies read.

The key rule: ARIA supplements HTML semantics. It doesn't replace them. If a native HTML element exists for your use case, use it. ARIA is for cases where HTML semantics are insufficient — custom widgets, complex interactive components, or cases where the visual label isn't accessible to screen readers.

The first rule of ARIA: no ARIA is better than bad ARIA. A broken ARIA implementation actively harms screen reader users. Untouched HTML with native semantics is a safer baseline than incorrect ARIA attributes.


The Four ARIA Labeling Mechanisms

1. aria-label

Provides a string label directly on the element. Overrides all other labeling mechanisms.

<!-- Icon-only button with no visible text -->
<button aria-label="Close dialog">
  <svg aria-hidden="true">...</svg>
</button>

<!-- Search input without a visible <label> -->
<input type="search" aria-label="Search products" />

Use when: There's no visible text element that can serve as the label — typically icon-only controls, or inputs where the design intentionally omits a <label>.

Don't use when: A visible label exists. Use aria-labelledby instead (connecting screen reader output to the visible text keeps them in sync automatically).

2. aria-labelledby

References one or more existing elements by ID. The screen reader announces the concatenated text content of those elements.

<!-- Dialog title labels the dialog itself -->
<div role="dialog" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Confirm deletion</h2>
  <p>This action cannot be undone.</p>
</div>

<!-- Multiple elements form the label -->
<div id="product-name">Running Shoes</div>
<div id="product-size">Size 10</div>
<button aria-labelledby="product-name product-size">Add to cart</button>
<!-- Screen reader announces: "Running Shoes Size 10, button" -->

Use when: A visible label element exists in the DOM. This is the preferred pattern over aria-label because the accessible name stays synchronized with the visible text.

Multiple IDs: Space-separate them. The text content of all referenced elements is concatenated in order.

3. aria-describedby

Like aria-labelledby, but provides a description rather than a label. The label identifies what an element is; the description provides additional context announced after the label.

<label for="password">Password</label>
<input
  type="password"
  id="password"
  aria-describedby="password-requirements"
/>
<p id="password-requirements">
  Must be 8+ characters and include one number.
</p>
<!-- Screen reader: "Password, edit text. Must be 8+ characters and include one number." -->

Use when: Additional instructions, error messages, or contextual hints supplement the primary label.

Error messages:

<input
  type="email"
  id="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<p id="email-error" role="alert">
  Enter a valid email address.
</p>

4. Native <label> element

For form inputs, a properly associated <label> is always preferred over ARIA:

<!-- Explicit association via for/id -->
<label for="username">Username</label>
<input type="text" id="username" />

<!-- Implicit association (wrapping) -->
<label>
  Username
  <input type="text" />
</label>

A <label> does two things ARIA can't: it provides an accessible name AND makes the label clickable to focus the input (improving usability for everyone). Use aria-label or aria-labelledby on inputs only when a visible <label> is genuinely not feasible.


Common ARIA Patterns

Accessible icon buttons

The most common ARIA use case. Icon-only buttons have no visible text, so they need aria-label:

<!-- Good -->
<button type="button" aria-label="Open menu">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

<!-- Also good if you have a visible tooltip span -->
<button type="button" aria-labelledby="menu-label">
  <svg aria-hidden="true" focusable="false">...</svg>
  <span id="menu-label" class="sr-only">Open menu</span>
</button>

Always add aria-hidden="true" and focusable="false" to inline SVGs in buttons. Without these, some screen readers read the SVG title or try to navigate into the SVG paths.

Navigation landmarks

<nav aria-label="Main navigation">...</nav>
<nav aria-label="Breadcrumb">...</nav>

If a page has multiple <nav> elements, each needs a unique aria-label so screen reader users can distinguish them in the landmarks list.

Modals and dialogs

<div
  role="dialog"
  aria-modal="true"
  aria-labelledby="modal-title"
  aria-describedby="modal-desc"
>
  <h2 id="modal-title">Delete account</h2>
  <p id="modal-desc">
    This permanently removes your data. This action cannot be undone.
  </p>
  <button>Cancel</button>
  <button>Delete</button>
</div>

aria-modal="true" tells screen readers that content outside the dialog is inert. You must also implement actual focus trapping in JavaScript — ARIA alone doesn't do it.

Live regions

For dynamic content updates (notifications, search results, loading states):

<!-- Polite: waits for current announcement to finish -->
<div aria-live="polite" aria-atomic="true" id="status">
  3 results found
</div>

<!-- Assertive: interrupts — use sparingly for truly urgent updates -->
<div aria-live="assertive" role="alert">
  Session expired. Please log in again.
</div>

aria-atomic="true" means the entire region is re-announced when any part changes, rather than just the changed portion.


Frequently Made Mistakes

Adding aria-label to elements that already have a label

<!-- Bad: redundant aria-label conflicts with visible label -->
<label for="email">Email address</label>
<input type="email" id="email" aria-label="Email" />

<!-- Good: let the <label> do its job -->
<label for="email">Email address</label>
<input type="email" id="email" />

Labeling non-interactive elements unnecessarily

<!-- Bad: <p> is not interactive, doesn't need a label -->
<p aria-label="Introduction paragraph">Welcome to our site.</p>

<!-- Good: just write the paragraph -->
<p>Welcome to our site.</p>

ARIA labels on static text elements like <p>, <div>, or <span> are usually ignored or cause confusion. Labels are for interactive elements and structural landmarks.

Generic labels that don't distinguish actions

<!-- Bad: two "Edit" buttons with no context -->
<button aria-label="Edit">Edit</button>
<button aria-label="Edit">Edit</button>

<!-- Good: distinguish by what is being edited -->
<button aria-label="Edit billing address">Edit</button>
<button aria-label="Edit shipping address">Edit</button>

Screen reader users often pull up a list of all interactive elements on a page. Identical labels make navigation impossible.

Using role="button" on a <div> instead of a <button>

<!-- Bad: requires manual keyboard handling and ARIA roles -->
<div role="button" aria-label="Submit" tabindex="0">Submit</div>

<!-- Good: native button has all this built in -->
<button type="submit">Submit</button>

Native <button> handles focus, keyboard activation (Enter and Space), and semantics automatically. Reserve ARIA roles for genuinely custom components.


Testing Your ARIA Implementation

Screen reader testing is non-negotiable. Computed accessibility trees and automated tools miss real-world behavior:

  • macOS VoiceOver: Cmd+F5 to toggle. Use the Web Rotor (Ctrl+Option+U) to list headings, links, landmarks, and form controls.
  • NVDA (Windows, free): Common screen reader for Windows testing. Use Browse Mode (arrows to navigate) vs Focus Mode (Tab for interactive elements).
  • JAWS (Windows): Most common enterprise screen reader. Test critical flows if your users are likely on enterprise machines.

Automated checks:

  • axe DevTools browser extension — flags many common ARIA errors
  • eslint-plugin-jsx-a11y — catches ARIA issues in JSX at write time

Browser accessibility panel:

  • Chrome: DevTools → Elements → Accessibility tab — shows the computed accessible name and role for any element
  • Firefox: DevTools → Accessibility panel — lists all elements with their ARIA properties

Quick Reference

Attribute What It Does Best For
aria-label Sets accessible name directly Icon buttons, unlabeled inputs
aria-labelledby Accessible name from existing element Dialogs, sections, complex widgets
aria-describedby Adds description after label Error messages, instructions, hints
aria-hidden="true" Hides from accessibility tree Decorative icons, duplicated text
aria-live="polite" Announces dynamic updates Status messages, search results
aria-live="assertive" Interrupts to announce Errors, urgent alerts
aria-expanded Toggle open/close state Accordions, dropdowns, menus
aria-invalid="true" Marks field as invalid Form validation errors
aria-required="true" Marks field as required When not using required attribute

The required HTML attribute is always preferred over aria-required for form fields — it does the same thing plus triggers native browser validation.