ARIA Basics: When (and When Not) to Use It
Learn the first rule of ARIA, plus aria-label, aria-labelledby, aria-describedby, aria-hidden, aria-expanded and aria-live, with real custom-widget examples.
What you will learn
ARIA (Accessible Rich Internet Applications) is a set of extra attributes that describe things to assistive technology when plain HTML can’t do it on its own. It sounds like a powerful upgrade, and used correctly, it is — but ARIA is also one of the most commonly misused tools in web development, often making pages less accessible than doing nothing at all. This lesson teaches you when ARIA genuinely helps, and — just as important — when to leave it alone entirely.
The first rule of ARIA
There’s an actual, official rule, published by the W3C, that every ARIA lesson should open with:
“No ARIA is better than bad ARIA.” If a native HTML element or attribute already has the behaviour or semantics you need, use it, instead of re-purposing an element and adding ARIA to make it accessible.
In practice, this means:
<!-- Don't do this -->
<div role="button" tabindex="0" onclick="submitForm()">Submit</div>
<!-- Do this -->
<button onclick="submitForm()">Submit</button>
The <div role="button"> version looks like it solves the accessibility problem — it even announces as “button” to a screen reader. But role="button" only adds the announcement. It does not add keyboard focusability, Enter/Space activation, or any of the other behaviours a real <button> gets automatically. You’d have to manually rebuild every one of those yourself with JavaScript (tabindex="0", listening for both click and keydown events, and so on) — and it’s extremely easy to get one of them subtly wrong. A real <button> gets all of it correct, for free, forever.
ARIA exists for the genuine gaps — situations where no native HTML element does what you need, typically custom, JavaScript-driven interface components like a custom dropdown, a tab panel, or an accordion. It is a patch for missing native semantics, not a general-purpose accessibility fix.
aria-label: naming something with no visible text
<button aria-label="Close dialog">✕</button>
<nav aria-label="Main navigation">...</nav>
aria-label provides an invisible name read by screen readers, for an element whose visible content doesn’t clearly say what it does. A common, genuine use case: an icon-only button. Sighted users understand a “✕” icon means “close” from visual convention; a screen reader has no such convention to fall back on and needs the name spelled out directly.
You met a second real use in the accessibility lesson: distinguishing two <nav> landmarks that would otherwise both just announce as “navigation.”
Don’t use
aria-labelon something that already has clear, readable visible text.<button aria-label="Submit the form">Submit</button>is redundant — the button’s own text already says exactly that.
aria-labelledby: naming something using another element already on the page
<h2 id="billing-heading">Billing Address</h2>
<section aria-labelledby="billing-heading">
<label for="street">Street</label>
<input type="text" id="street" name="street">
</section>
Instead of writing a name inline as text (like aria-label), aria-labelledby points to the id of another element already on the page and uses that element’s text as the name. This avoids repeating the same words twice, and keeps the name in sync automatically if that heading’s text ever changes.
aria-describedby: adding extra explanatory context
<label for="password">Password</label>
<input type="password" id="password" name="password" aria-describedby="password-hint">
<p id="password-hint">Must be at least 8 characters, with one number.</p>
Similar to aria-labelledby, but for supplementary description rather than the primary name — think of it as the accessible version of a helper hint under a form field. A screen reader announces the field’s label first, then this extra description, giving the same context a sighted user gets from reading the hint text visually.
aria-hidden: hiding decorative content from assistive tech
<button>
<span aria-hidden="true">★</span>
Add to favourites
</button>
aria-hidden="true" tells assistive technology to skip an element entirely, even though it’s still visually present on the page. This is the right tool for a purely decorative icon sitting next to real text that already conveys the meaning — the star icon adds nothing a screen reader user needs, since “Add to favourites” already says it in words.
Critical rule: never put
aria-hidden="true"on an element that can receive keyboard focus (a link, a button, an input). Doing so creates a genuinely broken experience — a keyboard user can still Tab their way onto the element and interact with it, while a screen reader user, told to ignore it, gets no announcement of what they just landed on.aria-hiddenis for purely decorative, non-interactive content only.
aria-expanded: state for toggles, accordions and dropdowns
<button aria-expanded="false" aria-controls="faq-answer-1">
What payment methods do you accept?
</button>
<div id="faq-answer-1" hidden>
<p>We accept credit cards, UPI, and net banking.</p>
</div>
aria-expanded announces whether a collapsible section is currently open ("true") or closed ("false") — essential for any accordion, dropdown menu, or “show more” toggle you build with JavaScript. Without it, a screen reader user hears the button’s text but has no way to know its current state, or that pressing it toggles anything at all.
aria-controls (used alongside it above) links the button to the id of the content it controls, so assistive technology understands the relationship between the trigger and what it reveals.
This value must be kept in sync with actual behaviour via JavaScript. Setting
aria-expanded="false"in your HTML and then never updating it to"true"when the visitor actually expands the section is worse than not having it at all — it actively tells screen reader users the wrong thing. You’ll wire this update up properly once you reach the JavaScript DOM lessons; for now, understand what the attribute communicates and why it needs to change dynamically.
aria-live: announcing dynamic content changes
<div role="alert" aria-live="assertive">
Your changes have been saved.
</div>
Normally, a screen reader only reads what’s on the page when the user actively navigates to it. But some content appears without any navigation at all — a “Saved!” confirmation message, a form validation error, a live chat notification. aria-live tells the screen reader to announce new content in this region automatically, the moment it appears, even though the user’s focus never moved there.
| Value | Behaviour |
|---|---|
polite |
Announces the change, but waits until the user is idle — won’t interrupt whatever they’re currently doing |
assertive |
Announces immediately, interrupting anything currently being read — reserve this for genuinely urgent messages |
role="alert" (shown above) is actually a convenient shortcut — it automatically implies aria-live="assertive" behaviour, specifically meant for important, time-sensitive messages like errors.
A complete, real-world example: a custom accordion
<h2>Frequently Asked Questions</h2>
<button
id="faq-trigger-1"
aria-expanded="false"
aria-controls="faq-panel-1"
>
What payment methods do you accept?
</button>
<div id="faq-panel-1" role="region" aria-labelledby="faq-trigger-1" hidden>
<p>We accept credit cards, UPI, and net banking.</p>
</div>
Read through what each piece does: the button is a real, keyboard-accessible <button> (no role="button" needed — it’s already native). aria-expanded communicates open/closed state. aria-controls links it to the panel it reveals. The panel itself uses aria-labelledby pointing back at the button, so a screen reader user who lands inside the panel directly (rather than via the button) still knows what question it’s answering. hidden (the plain HTML boolean attribute you met earlier) actually hides the panel — JavaScript would toggle both hidden and aria-expanded together when the visitor clicks.
Note: for a native alternative to a hand-built accordion like this,
<details>/<summary>(covered in the next lesson) already handles almost all of this automatically, with zero ARIA needed. Reach for it first when a plain expand/collapse is all you need; build a custom ARIA-powered version like the one above only when you need behaviour<details>genuinely can’t provide.
Common mistakes
- Adding
role="button"(or similar) to a<div>instead of using the real native element. This is the single most common ARIA misuse — remember the first rule. - Using
aria-labelon an element that already has clear visible text, creating redundant or conflicting announcements. - Setting
aria-hidden="true"on a focusable element, creating a confusing gap between what keyboard users can reach and what screen readers announce. - Setting
aria-expandedonce in the HTML and never updating it with JavaScript when the state actually changes. - Overusing
aria-live="assertive"for routine, non-urgent updates, which is disruptive and defeats the purpose of having a “polite” option at all. - Adding ARIA attributes “just in case,” without understanding what they announce. Wrong or redundant ARIA is genuinely worse than none, since it actively misleads assistive technology rather than simply saying nothing.
Interview-style questions
What is “the first rule of ARIA”?
If a native HTML element already provides the semantics and behaviour you need, use that element instead of adding ARIA to a generic element like a <div> — native elements are almost always more complete and more reliable.
What’s the difference between aria-label and aria-labelledby?
aria-label provides the accessible name directly as a string in the attribute itself. aria-labelledby instead points to the id of another element already on the page and uses that element’s own text as the name.
Why is putting aria-hidden="true" on a focusable element a serious problem?
It creates a mismatch: a keyboard user can still Tab onto and interact with the element, but a screen reader is told to skip it entirely, so that user gets no announcement of what they’ve just focused.
What does aria-live="polite" do, and how does it differ from "assertive"?
Both announce dynamically appearing content without requiring the user to navigate to it. polite waits until the user is idle before announcing; assertive interrupts immediately and should be reserved for genuinely urgent messages.
Practice
-
Use the Practice in Editor button. Change the accordion button’s
aria-expandedvalue to"true"and removehiddenfrom the panel below it, to see what the “open” state would look like. -
Add a second icon-only button with an appropriate
aria-label, for something like “Delete item.” -
Rewrite this accordion trigger to use
<details>/<summary>instead (you’ll fully cover this element in the next lesson — just take a first guess based on what you’ve seen so far), and explain why it needs less ARIA. -
Fix this code (it has two mistakes:
role="button"on a<div>instead of a real button, andaria-hidden="true"on something focusable):<div role="button" tabindex="0" onclick="closeModal()" aria-hidden="true">Close</div>
Recap
- The first rule of ARIA: use a native HTML element when one already exists for the job, before reaching for ARIA on a generic
<div>or<span>. aria-labelnames an element with invisible text;aria-labelledbyreuses another element’s existing text as the name instead.aria-describedbylinks supplementary explanatory text to a field, like a helper hint.aria-hidden="true"hides purely decorative content from assistive tech — never apply it to a focusable element.aria-expandedandaria-controlscommunicate and connect toggle/accordion state, and must stay in sync with actual behaviour via JavaScript.aria-live(politeorassertive) announces dynamically appearing content without requiring the user to navigate to it.