ARIA Labels Explained: A Practical Guide to aria-label, aria-labelledby, and aria-describedby
If you've ever stared at aria-label, aria-labelledby, and aria-describedby wondering which one to use, you're not alone. These are some of the most useful tools in web accessibility, but they're also some of the most misused. I see ARIA mistakes in nearly every scan I run with our free checker.
In this guide, I'll walk you through each attribute with real code examples, show you exactly when to use which one, and cover the mistakes that trip up even experienced developers.
In This Article
What Are ARIA Attributes?
WAI-ARIA (Web Accessibility Initiative – Accessible Rich Internet Applications) is a set of HTML attributes that give extra information to screen readers. They don't change how anything looks or behaves for sighted users. They only change what gets announced.
The attributes I'm covering here solve one specific problem: telling screen readers what your UI elements are called and what they do. You can see that a magnifying glass icon means "search." A screen reader user needs you to spell that out in text.
The First Rule of ARIA
If you can use a native HTML element or attribute with the semantics and behavior you need, do that instead of adding ARIA. A <button> is always better than <div role="button">. A <label> element is always better than aria-label on an input. ARIA is for situations where native HTML can't express what you need.
Here's the quick breakdown of the four attributes we'll cover:
aria-label: Provides an invisible text label directly on an elementaria-labelledby: Points to another visible element whose text content becomes the labelaria-describedby: Points to another element whose text provides a supplemental descriptionaria-hidden: Hides an element from screen readers entirely
aria-label: Adding Invisible Labels
aria-label provides a text label directly on an element. The text is invisible to sighted users but announced by screen readers. Use it when there's no visible text that identifies an interactive element.
When to Use aria-label
- Icon-only buttons (close, menu, search, play)
- Navigation landmarks that need disambiguation
- Controls where the visual context is obvious but the programmatic name is missing
Icon Buttons
The most common use case. An icon button with no visible text needs aria-label to tell screen readers what it does:
<!-- Bad: screen reader announces "button" with no name -->
<button class="close-btn">
<svg>...</svg>
</button>
<!-- Good: screen reader announces "Close dialog" -->
<button class="close-btn" aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
Hamburger Menu
Your site's own nav toggle is a perfect example:
<button class="nav-toggle"
aria-expanded="false"
aria-controls="main-nav"
aria-label="Menu">
<span></span>
<span></span>
<span></span>
</button>
Without aria-label="Menu", a screen reader would announce this as just "button," since three empty <span> elements provide zero text content.
Search Inputs
When a search field has no visible <label> element:
<!-- Acceptable: aria-label replaces a missing visible label -->
<input type="search" aria-label="Search articles">
<button type="submit">Search</button>
<!-- Better: use a visually hidden label element -->
<label for="search" class="visually-hidden">Search articles</label>
<input type="search" id="search">
<button type="submit">Search</button>
Keep aria-label Short
An aria-label replaces the element's accessible name. Keep it concise. Think of it as what you'd put on a visible label. "Close" or "Close dialog" is fine. "Click this button to close the dialog and return to the main page" is too long.
Disambiguating Landmarks
When a page has multiple <nav> elements, use aria-label to distinguish them:
<nav aria-label="Main">
<a href="/">Home</a>
<a href="/blog">Blog</a>
</nav>
<nav aria-label="Breadcrumb">
<a href="/">Home</a> › <a href="/blog">Blog</a> › Current Page
</nav>
Screen reader users can pull up a list of landmarks. If both say "navigation," that's useless. Labeling them "Main" and "Breadcrumb" tells the user exactly which is which.
aria-labelledby: Referencing Visible Text
aria-labelledby references the id of one or more existing elements. The text content of those elements becomes the accessible name. Unlike aria-label, it points to text that's already visible on the page.
When to Prefer aria-labelledby Over aria-label
- A visible heading or label already describes the element
- You need to compose a label from multiple text fragments
- You want the accessible name to stay in sync with the visible text (if the visible text changes, the label updates automatically)
Modal Dialogs
A dialog should be labeled by its visible heading:
<div role="dialog" aria-labelledby="dialog-title" aria-modal="true">
<h2 id="dialog-title">Delete your account</h2>
<p>This action cannot be undone. All your data will be permanently removed.</p>
<button>Cancel</button>
<button>Delete</button>
</div>
When the dialog opens, a screen reader announces "Delete your account, dialog." The user immediately knows what they're dealing with.
Form Sections
Use aria-labelledby to label a form group by its visible heading:
<div role="group" aria-labelledby="billing-heading">
<h3 id="billing-heading">Billing Address</h3>
<label for="street">Street</label>
<input type="text" id="street">
<label for="city">City</label>
<input type="text" id="city">
</div>
Composing Labels from Multiple Elements
aria-labelledby accepts multiple IDs, separated by spaces. The accessible name is the concatenated text of all referenced elements in order. This is useful for table-like layouts or repeated controls that need context:
<!-- Screen reader announces: "Quantity, T-Shirt" -->
<span id="qty-label">Quantity</span>
<span id="product-name">T-Shirt</span>
<input type="number" aria-labelledby="qty-label product-name" value="1">
Regions and Sections
Label page regions using their visible headings:
<section aria-labelledby="latest-heading">
<h2 id="latest-heading">Latest Articles</h2>
<!-- article cards -->
</section>
<section aria-labelledby="popular-heading">
<h2 id="popular-heading">Most Popular</h2>
<!-- article cards -->
</section>
aria-describedby: Adding Supplemental Info
aria-describedby works like aria-labelledby syntactically, as it references one or more element IDs. But semantically it's different: it provides a description, not a name. Screen readers announce the description after the name and role, usually with a brief pause.
Think of it this way: the label tells you what something is. The description tells you more about it.
Password Requirements
<label for="password">Password</label>
<input type="password" id="password"
aria-describedby="password-help">
<p id="password-help">
Must be at least 8 characters with one uppercase letter and one number.
</p>
A screen reader announces: "Password, edit text. Must be at least 8 characters with one uppercase letter and one number."
Error Messages
Link inline error messages to the field they describe. This is one of the most impactful ARIA patterns for accessible forms:
<label for="email">Email address</label>
<input type="email" id="email"
aria-describedby="email-error"
aria-invalid="true">
<p id="email-error" class="error" role="alert">
Please enter a valid email address (e.g., [email protected]).
</p>
The aria-invalid="true" tells the screen reader the field has an error. The aria-describedby links the error text so it's announced when the field is focused. The role="alert" causes the error to be announced immediately when it appears in the DOM.
Help Text for Complex Inputs
<label for="dob">Date of birth</label>
<input type="text" id="dob"
aria-describedby="dob-format">
<p id="dob-format">Format: MM/DD/YYYY</p>
Combining with Multiple IDs
Like aria-labelledby, you can reference multiple elements:
<label for="card">Credit card number</label>
<input type="text" id="card"
aria-describedby="card-format card-error">
<p id="card-format">16 digits, no spaces or dashes.</p>
<p id="card-error" class="error">Card number is invalid.</p>
The screen reader announces both descriptions in order: the format hint first, then the error message.
Label vs Description: What Gets Announced When?
When a user focuses an element, the screen reader announces in this order: accessible name (from label/aria-label/aria-labelledby) → role (button, textbox, etc.) → state (checked, expanded, etc.) → description (from aria-describedby). Some screen readers let users skip the description, so never put critical labeling information in aria-describedby alone.
aria-label vs aria-labelledby: When to Use Which
Both attributes set the accessible name of an element, but they work differently. Here's a direct comparison:
| Feature | aria-label | aria-labelledby |
|---|---|---|
| Source of label text | String value on the attribute itself | Text content of referenced element(s) |
| Visible to sighted users? | No | Yes (references visible text) |
| Can reference multiple sources? | No (single string only) | Yes (space-separated IDs) |
| Translatable? | Not by default (hardcoded string) | Yes (references DOM text, which gets translated) |
| Stays in sync with visible text? | No (must be manually updated) | Yes (automatically reflects changes) |
| Overrides native label? | Yes | Yes (highest priority in name calculation) |
| Best for | Icon buttons, landmarks, elements with no visible label | Dialogs, sections, form groups with visible headings |
Decision Guide
Follow this sequence to pick the right attribute:
- Is there a native HTML mechanism? Use
<label for="...">,<legend>,<caption>, or<figcaption>instead of any ARIA attribute. Stop here if possible. - Is there visible text on the page that already labels this element? Use
aria-labelledbyand reference it by ID. - Is there no visible text at all? Use
aria-labelwith a concise string. - Do you need to add extra context beyond the name? Use
aria-describedbyin addition to the label.
Priority Order
If an element has both aria-labelledby and aria-label, aria-labelledby wins. If it has aria-label and a native <label>, aria-label wins. The full priority: aria-labelledby > aria-label > native <label> > title attribute > placeholder.
aria-hidden: Hiding from Screen Readers
aria-hidden="true" removes an element and all its children from the accessibility tree. Screen readers skip it entirely. The element remains visible on screen; this only affects assistive technology.
When to Use aria-hidden
Decorative Icons
Icons that are purely decorative or redundant with adjacent text should be hidden from screen readers:
<!-- Good: icon is decorative, button text provides the label -->
<button>
<svg aria-hidden="true">...</svg>
Download PDF
</button>
<!-- Good: icon is decorative next to link text -->
<a href="/settings">
<i class="icon-gear" aria-hidden="true"></i>
Settings
</a>
Duplicated Content
When a visual element duplicates information already provided in an accessible way:
<!-- The visual asterisk is redundant with aria-required -->
<label for="name">
Full name <span aria-hidden="true">*</span>
</label>
<input type="text" id="name" required aria-required="true">
Decorative Separators and Backgrounds
<!-- Decorative divider adds no information -->
<hr aria-hidden="true">
<!-- Background animation is purely visual -->
<div class="particle-background" aria-hidden="true"></div>
Dangerous Misuses of aria-hidden
Never Hide Focusable Elements
If an element has aria-hidden="true" but is still focusable (links, buttons, inputs), screen reader users can tab to it but hear nothing. This creates a confusing "ghost" element. If you hide something with aria-hidden, make sure it also has tabindex="-1" or is truly not interactive.
<!-- DANGEROUS: link is hidden from screen readers but still focusable -->
<div aria-hidden="true">
<a href="/settings">Settings</a>
</div>
<!-- CORRECT: if you must hide a section, remove focusability too -->
<div aria-hidden="true">
<a href="/settings" tabindex="-1">Settings</a>
</div>
<!-- BEST: don't use aria-hidden on sections with interactive content -->
Never Use aria-hidden on the Body or Main Content
Adding aria-hidden="true" to <body> or a main content wrapper makes your entire page invisible to screen readers. This sometimes happens accidentally with modal dialog implementations. When opening a modal, apply aria-hidden="true" only to the background content behind the modal, never to the modal itself or the body element.
aria-hidden="false" Does Not Unhide
A common misconception: setting aria-hidden="false" on a child inside an aria-hidden="true" parent does not make the child visible again. Once a parent is hidden from the accessibility tree, all descendants are hidden too. There is no override.
<!-- This does NOT work: the button is still hidden -->
<div aria-hidden="true">
<button aria-hidden="false">Save</button>
</div>
Common ARIA Mistakes
ARIA errors are among the most frequent issues we find in accessibility scans. Here are the patterns that cause real problems:
Mistake 1: Using ARIA When Native HTML Works
The single most common ARIA mistake is using it unnecessarily. Every ARIA attribute you add is a maintenance burden and a potential source of bugs.
<!-- Bad: ARIA duplicates what native HTML already provides -->
<div role="button" tabindex="0" aria-label="Submit">Submit</div>
<!-- Good: native button does everything automatically -->
<button type="submit">Submit</button>
<!-- Bad: aria-label duplicates the visible label -->
<label for="email">Email</label>
<input type="email" id="email" aria-label="Email">
<!-- Good: the label element is sufficient -->
<label for="email">Email</label>
<input type="email" id="email">
Mistake 2: aria-label on Non-Interactive Elements
aria-label is intended for interactive elements (buttons, links, inputs) and landmarks (nav, main, region). Placing it on a <div>, <span>, or <p> has inconsistent screen reader support and often gets ignored entirely.
<!-- Bad: aria-label on a plain div (often ignored) -->
<div aria-label="Important notice">
Your account expires in 3 days.
</div>
<!-- Good: use a heading or visible text instead -->
<div>
<h3>Important notice</h3>
<p>Your account expires in 3 days.</p>
</div>
<!-- Also good: if you need landmark semantics, use role="region" -->
<div role="region" aria-label="Account notice">
<p>Your account expires in 3 days.</p>
</div>
Mistake 3: Conflicting Labels
When aria-label says one thing and the visible text says another, sighted speech-input users (e.g., Dragon NaturallySpeaking) can't activate the control. They say what they see, but the accessible name is different.
<!-- Bad: visible text says "Submit" but accessible name is "Send form data" -->
<button aria-label="Send form data">Submit</button>
<!-- Good: aria-label matches or starts with the visible text -->
<button aria-label="Submit order form">Submit</button>
<!-- Best: just let the visible text be the label -->
<button>Submit</button>
WCAG SC 2.5.3, Label in Name
WCAG 2.2 requires that when an element has visible text, its accessible name must contain that visible text. This means aria-label should include (or match) the visible label text. This is a Level A requirement and a common WCAG failure.
Mistake 4: Broken aria-labelledby References
If aria-labelledby references an id that doesn't exist in the DOM, the element ends up with no accessible name at all. This is worse than having no ARIA, because the broken reference overrides any native label.
<!-- Bad: "heading-title" doesn't exist in the DOM -->
<section aria-labelledby="heading-title">
<h2 id="section-title">Our Services</h2>
</section>
<!-- Good: IDs match -->
<section aria-labelledby="section-title">
<h2 id="section-title">Our Services</h2>
</section>
Mistake 5: Over-Labeling
Adding aria-label to everything "just in case" creates noise. Screen reader users hear unnecessary announcements on every element, making the page harder to navigate.
<!-- Bad: aria-label adds no value here -->
<img src="dog.jpg" alt="Golden retriever in a park"
aria-label="Photo of a golden retriever playing in a park">
<!-- Good: alt text is sufficient -->
<img src="dog.jpg" alt="Golden retriever in a park">
Similarly, don't add aria-label to elements that already have a perfectly good text label from their content, a <label> element, or an alt attribute. ARIA should fill gaps, not duplicate what's already there.
Testing ARIA with Screen Readers
The only way to truly verify ARIA is working correctly is to test with an actual screen reader. Automated scanners like Free ADA Checker catch missing labels, broken references, and misused roles, but they can't tell you whether the experience makes sense.
Quick Testing Steps
- VoiceOver (macOS): Press
Cmd + F5to toggle VoiceOver. UseTabto move through interactive elements. Listen for each element's announced name, role, and description. PressVO + U(Ctrl + Option + U) to open the rotor and browse landmarks, headings, and form controls. - NVDA (Windows): Download NVDA (free) and press
Insert + Spaceto toggle focus/browse modes. Tab through your form controls and buttons. PressInsert + F7to list all links, headings, and landmarks. - Chrome DevTools: Right-click an element → Inspect → Accessibility tab. Check the "Computed Properties" section to see the resolved accessible name and description. This shows you exactly what a screen reader will announce.
What to Listen For
- Does every button and link have a meaningful name?
- Are form fields announced with their label and any description text?
- Do landmarks (nav, main, regions) have unique labels?
- Are decorative elements silent?
- Do error messages get announced when they appear?
For a detailed walkthrough of screen reader testing, see our complete screen reader testing guide.
Browser DevTools Shortcut
In Chrome, you can quickly check any element's accessible name without a screen reader: open DevTools, select the element in the Elements panel, and look at the Accessibility pane. The "Name" field shows exactly what aria-label, aria-labelledby, or native labeling resolved to.
Scan Your Site for ARIA Issues
Run a free accessibility scan to detect missing labels, broken ARIA references, and misused roles across your pages. For more on fixing common issues, see our guides on common WCAG mistakes, accessible forms, and writing effective alt text.