ARIA (Accessible Rich Internet Applications) attributes add information for assistive technology, such as screen readers. It is powerful, and it is also easy to misuse.
The first rule of ARIA
> Do not use ARIA if a native HTML element already does the job.
<button>, <a>, <input>, <nav>, <main>, <dialog> and friends carry the right meaning and behaviour built in. ARIA only changes what is announced, never how things behave. A <div role="button"> still needs focus, Enter and Space handling written by you.
<!-- Bad: needs a role, tabindex, key handling... -->
<div role="button" tabindex="0">Save</div>
<!-- Good -->
<button type="button">Save</button>
When ARIA is needed
When no native element describes what you built, or the state is changing dynamically.
aria-expanded: a disclosure
A button that shows and hides content should say whether it is open.
<style>[hidden] { display: none; }</style>
<button id="toggle" aria-expanded="false" aria-controls="details">More info</button>
<div id="details" hidden>Here is the extra information.</div>
<script>
const toggle = document.querySelector("#toggle");
const details = document.querySelector("#details");
toggle.addEventListener("click", () => {
const open = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", String(!open));
details.hidden = open;
});
</script>
Screen readers announce “More info, button, collapsed” and then “expanded” after activation. (The native <details> element does this with no JavaScript at all, so use it when it fits.)
aria-pressed: a toggle button
<button id="mute" aria-pressed="false">Mute</button>
<script>
const button = document.querySelector("#mute");
button.addEventListener("click", () => {
const pressed = button.getAttribute("aria-pressed") === "true";
button.setAttribute("aria-pressed", String(!pressed));
console.log("muted:", !pressed);
});
</script>
aria-label: a name when there is no text
An icon-only button has no accessible name unless you give one.
<button aria-label="Close dialog">×</button>
<button aria-label="Search"><svg aria-hidden="true">...</svg></button>
aria-hidden="true" hides decorative content from assistive technology. Never put it on something focusable.
Useful attributes
| Attribute | Use |
|---|---|
aria-label | a name when the element has no visible text |
aria-labelledby | name it with another element’s text |
aria-describedby | extra help or error text (as in forms) |
aria-expanded | open or closed |
aria-pressed | toggle buttons |
aria-current | the current page or step in a set |
aria-live | announce changes (Live Regions) |
aria-hidden | hide decoration |
role | only when no native element fits |
Keep it truthful
ARIA is a promise. If you write aria-expanded="true", the content must really be expanded. Update ARIA in the same place as the visual change, so they never disagree.
Common mistakes
- Using ARIA instead of HTML (
div role="button"for everything). - Wrong or contradictory roles (a
<button role="link">). - Forgetting to update states when JavaScript changes the page.
aria-labelthat repeats the visible text with different words (screen reader users hear something different from what is seen). Start the label with the visible text.- No keyboard support for custom roles.
Test with a screen reader
NVDA (Windows, free), VoiceOver (macOS and iOS) and TalkBack (Android) are built in or free. Even a quick check teaches a lot, and browser developer tools have an Accessibility panel showing the computed names and roles.