ARIA (Accessible Rich Internet Applications) is a set of HTML attributes that tell assistive technology what an element is and how to describe it. A role says what an element does; a label like aria-label gives it a name a screen reader can announce. The golden rule: use ARIA only when native HTML can’t do the job.
The first rule of ARIA: don’t use ARIA
The single most important thing to know about ARIA is when not to reach for it. The W3C states it plainly in the first rule of ARIA use: if a native HTML element or attribute already has the semantics and behavior you need, use it instead of repurposing a generic element and bolting on ARIA.
A real <button> is already announced as a button, is keyboard-focusable, fires on Enter and Space, and works in every screen reader. A <div role="button"> gives you the role name and nothing else — you now have to add tabindex, key handlers, and focus styles by hand, and most teams forget at least one. Native HTML is accessible by default; ARIA only describes, it never adds behavior.
| Don’t do this | Do this instead |
|---|---|
<div role="button" tabindex="0"> | <button> |
<span role="link"> | <a href="..."> |
<div role="checkbox"> | <input type="checkbox"> |
<div role="navigation"> | <nav> |
<div role="heading" aria-level="2"> | <h2> |
If you find yourself adding a role that exactly matches an existing HTML element, that’s the signal to switch to the element.
What ARIA actually is
ARIA fills gaps that plain HTML can’t. It does three jobs:
- Roles — define what an element is:
role="alert",role="dialog",role="tablist". They map a custom component to a known widget pattern so screen readers like NVDA, JAWS, and VoiceOver announce it correctly. - Properties — describe relationships and characteristics:
aria-labelledby,aria-describedby,aria-required,aria-haspopup. These are generally static. - States — describe the current condition:
aria-expanded="false",aria-checked="true",aria-disabled,aria-hidden. These change with user interaction, usually via JavaScript.
Crucially, ARIA changes only the accessibility tree — the information assistive technology reads. It does not change appearance, and it does not add keyboard behavior. That is the most common misunderstanding behind ARIA bugs.
Giving elements an accessible name
Every interactive control needs an accessible name — the text a screen reader speaks. The supporting WCAG 2.1 AA criterion is 4.1.2, Name, Role, Value. You have a few ways to provide one, in order of preference:
- Visible text content — a
<button>Submit</button>is named by its text. Nothing extra needed. aria-labelledby— points to theidof visible text already on the page. Best when a visible label exists, because the spoken name matches what users see.aria-label— a string you type into the attribute. Use it when there’s no visible text, like an icon-only button.
<!-- Icon-only button: no visible text, so aria-label provides the name -->
<button aria-label="Close dialog">
<svg aria-hidden="true">...</svg>
</button>
<!-- A search region named by visible heading text -->
<h2 id="search-heading">Search our menu</h2>
<form role="search" aria-labelledby="search-heading">...</form>
Note aria-hidden="true" on the decorative SVG inside the button — it stops the screen reader from announcing the icon, while the button’s aria-label still provides the name. For text alternatives on real images, see our alt text guide.
Common ARIA roles and labels you’ll actually use
You don’t need the whole spec. A small set of roles and attributes covers most real sites.
| Pattern | Useful ARIA | Notes |
|---|---|---|
| Landmark regions | <nav>, <main>, role="search", role="banner" | Prefer native landmark elements; only use roles where no element exists. |
| Icon button | aria-label="..." | Names a control that has no visible text. |
| Toggle / disclosure | aria-expanded, aria-controls | Update aria-expanded with JavaScript when state changes. |
| Modal dialog | role="dialog", aria-modal="true", aria-labelledby | Must also trap focus and restore it on close. |
| Live status message | role="status" or aria-live="polite" | Announces dynamic content without moving focus. |
| Required form field | aria-required="true", aria-describedby | Pair with a real <label>; see accessible forms. |
| Error message | role="alert" | Interrupts to announce a validation error immediately. |
For page structure, ARIA landmarks let screen-reader users jump straight to navigation, the main content, or search. In modern HTML, the native elements <header>, <nav>, <main>, and <footer> carry those landmark roles for free — our heading structure and landmarks guide covers how they fit together.
Frequent ARIA misuse
This is where ARIA earns its bad reputation. WebAIM’s annual analysis of home pages has repeatedly found that pages with ARIA average more detected accessibility errors than pages without it — not because ARIA is bad, but because it’s so often applied incorrectly. The usual culprits:
- Roles that contradict the element.
<a role="button">confuses everyone; a link that acts like a button should be a<button>. - Labeling that hides content. Putting
aria-labelon a container can override the real, visible text inside it, so screen-reader users hear less than sighted users. aria-hiddenon focusable elements. Hiding an element that can still receive keyboard focus creates a “ghost” stop — the user lands on something the screen reader refuses to name.- Redundant ARIA.
role="button"on a<button>, oraria-labelthat just repeats the visible text, adds noise and risk for zero benefit. - States that never update.
aria-expanded="false"left hard-coded so it never flips totruetells the user the menu is always closed, even when it’s open. - Invalid references.
aria-labelledby="foo"pointing to anidthat doesn’t exist produces no name at all.
The thread tying these together: ARIA describes, it never implements. Every custom widget still needs working keyboard navigation and visible focus. ARIA without the matching behavior is a label on an empty box.
How to verify ARIA is correct
ARIA is exactly the kind of thing automated scanners miss. A tool can flag an invalid aria-labelledby reference, but it can’t tell you whether a role="button" actually responds to the Space key, or whether the spoken name makes sense in context. That gap is why we lean on manual accessibility testing and real assistive technology.
- Test with a real screen reader. Tab through every custom control with NVDA, JAWS, or VoiceOver and listen to what’s announced. The name, role, and state should all be spoken and accurate.
- Inspect the accessibility tree. Browser dev tools show the computed name and role for any element — the fastest way to confirm an
aria-labelis winning or losing. - Check keyboard behavior alongside ARIA. If a
role="tab"doesn’t move with arrow keys, the role is a promise the code doesn’t keep. - Validate against WCAG. Map findings back to Name, Role, Value (4.1.2) and the relevant POUR principles during your accessibility audit.
Why overlays make ARIA worse, not better
Accessibility overlay widgets — the kind sold by accessiBe, UserWay, and AudioEye — promise to “fix” sites automatically. With ARIA, they reliably make things worse. An overlay can’t know that your <div> is meant to be a tab, what a button should be named, or which dynamic region should announce updates. It injects generic ARIA at runtime, and that guesswork frequently introduces the exact misuse described above. That’s a big part of why overlays don’t work, and a recurring theme in the thousands of ADA web accessibility lawsuits and demand letters filed in the US each year — many target sites that already had an overlay installed.
Correct ARIA is a human judgment call, component by component. That’s what Curbcut does: real, manual accessibility remediation where every role, label, and state is verified against the actual markup and tested with a screen reader. No widget, no guessing.
Not sure where your site stands? Start with a free accessibility scan and we’ll show you which ARIA issues are real and how we’d fix them.