AccessibilityEasy

How do aria-label, aria-labelledby, and aria-live work?

01

The Short Answer

aria-label, aria-labelledby, and aria-live are ARIA attributes that make web content accessible to screen readers and assistive technologies. aria-label provides an invisible text label for an element, aria-labelledby points to another element whose text serves as the label, and aria-live tells screen readers to announce dynamic content changes. Together, they bridge the gap between visual UI and non-visual experiences.

02

aria-label

aria-label provides an accessible name for an element when there's no visible text label. It's read by screen readers but invisible on screen. Use it for icon buttons, inputs without visible labels, or any interactive element where the purpose isn't clear from the visible content alone.

aria-label-examples.htmlhtml
<!-- Icon button with no visible text -->
<button aria-label="Close dialog">
  <svg><!-- X icon --></svg>
</button>

<!-- Search input with no visible label -->
<input type="search" aria-label="Search articles" placeholder="Search..." />

<!-- Navigation landmark -->
<nav aria-label="Main navigation">
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>

<!-- Distinguishing multiple navs on the same page -->
<nav aria-label="Breadcrumb">
  <ol>...</ol>
</nav>

A screen reader will announce "Close dialog, button" for the first example. Without aria-label, it would just say "button" — leaving the user guessing what it does. Note that aria-label overrides any existing text content for assistive technology.

Prefer visible labels when possible

A visible <label> element is always better than aria-label because it helps all users, not just screen reader users. Use aria-label only when a visible label would harm the visual design or is redundant given the visual context.

03

aria-labelledby

aria-labelledby references one or more element IDs whose text content becomes the accessible name. Unlike aria-label (which takes a string directly), aria-labelledby points to existing visible text on the page. This keeps the label in sync with what sighted users see and avoids duplication.

aria-labelledby-examples.htmlhtml
<!-- Dialog labeled by its heading -->
<div role="dialog" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Delete Account</h2>
  <p>Are you sure? This action cannot be undone.</p>
  <button>Cancel</button>
  <button>Delete</button>
</div>

<!-- Input labeled by a nearby heading -->
<h3 id="billing-heading">Billing Address</h3>
<input type="text" aria-labelledby="billing-heading" />

<!-- Combining multiple label sources -->
<span id="name-label">Full Name</span>
<span id="name-required">(required)</span>
<input type="text" aria-labelledby="name-label name-required" />
<!-- Screen reader announces: "Full Name (required)" -->

When you pass multiple IDs (space-separated), the screen reader concatenates the text content of all referenced elements. This is useful for composing labels from multiple pieces of visible text without duplicating content in an aria-label string.

04

aria-label vs aria-labelledby

aria-labelaria-labelledby
ValueDirect stringID reference(s) to other elements
Visible?No — only for assistive techYes — references visible text
Stays in sync?Must be manually updatedAuto-syncs with referenced element's text
Multiple sources?NoYes — space-separated IDs
PrecedenceLowerHigher (overrides aria-label if both present)
Best forIcon buttons, inputs without visible labelsDialogs, sections, inputs with existing headings
05

aria-live

aria-live creates a "live region" — an area of the page that screen readers monitor for changes. When content inside a live region updates, the screen reader announces the change without the user needing to navigate to it. This is essential for dynamic content like notifications, form validation messages, chat messages, and loading states.

aria-live-examples.htmlhtml
<!-- Polite: announces after the screen reader finishes current speech -->
<div aria-live="polite" aria-atomic="true">
  <!-- Content injected here will be announced -->
  <p>3 results found</p>
</div>

<!-- Assertive: interrupts current speech immediately -->
<div aria-live="assertive" role="alert">
  <!-- Only for urgent messages -->
  <p>Session expired. Please log in again.</p>
</div>

<!-- Off: no announcements (default) -->
<div aria-live="off">
  <p>This won't be announced when it changes</p>
</div>

The live region must exist in the DOM before the content changes — you can't add aria-live and content at the same time and expect it to be announced. The pattern is: render the container with aria-live on mount, then inject or update content inside it when needed.

aria-live values

  • polite
    • Waits for the screen reader to finish speaking before announcing
    • Use for non-urgent updates: search results count, status messages, form feedback
  • assertive
    • Interrupts whatever the screen reader is currently saying
    • Use sparingly — only for urgent messages: errors, session expiry, critical alerts
  • off
    • No announcements (default behavior)
    • Use to temporarily silence a live region
06

Related Attributes

aria-live is often paired with aria-atomic and aria-relevant to fine-tune what gets announced. These modifiers control whether the entire region or just the changed part is read, and what types of changes trigger announcements.

AttributePurposeValues
aria-atomicAnnounce entire region or just changes?true (whole region) / false (just changes)
aria-relevantWhich changes trigger announcements?additions, removals, text, all
role="status"Implicit aria-live="polite"For status messages
role="alert"Implicit aria-live="assertive"For error/warning messages
07

React Example

In React, the live region container should be rendered on mount (even if empty), and the content should update via state changes. This ensures the screen reader is already monitoring the region when the content appears.

react-live-region.tsxtypescript
function SearchForm() {
  const [results, setResults] = useState<Item[]>([]);
  const [statusMessage, setStatusMessage] = useState('');

  async function handleSearch(query: string) {
    const data = await fetchResults(query);
    setResults(data);
    setStatusMessage(`${data.length} results found for "${query}"`);
  }

  return (
    <div>
      <input
        type="search"
        aria-label="Search articles"
        onChange={(e) => handleSearch(e.target.value)}
      />

      {/* Live region — always in DOM, content updates trigger announcements */}
      <div aria-live="polite" aria-atomic="true" className="sr-only">
        {statusMessage}
      </div>

      <ul>
        {results.map((item) => (
          <li key={item.id}>{item.title}</li>
        ))}
      </ul>
    </div>
  );
}

The sr-only class (visually hidden but accessible) is used here because the status message is only for screen readers — sighted users can see the results list directly. The live region announces the count so screen reader users know the search worked without navigating to the results.

08

Why Interviewers Ask This

This question tests your accessibility awareness and whether you build inclusive UIs. Interviewers want to see that you know when to use each attribute, understand the difference between labeling (aria-label/labelledby) and live announcements (aria-live), and can apply them in real components. It shows you think beyond visual users and understand that dynamic SPAs need extra work to be accessible.

Quick Revision Cheat Sheet

aria-label: Invisible text label — for icon buttons, unlabeled inputs

aria-labelledby: Points to visible text by ID — for dialogs, sections, composed labels

aria-live="polite": Announce changes after current speech — search results, status

aria-live="assertive": Interrupt immediately — errors, session expiry

aria-atomic: true = read entire region, false = read only changes

Key rule: Live region must exist in DOM before content changes