ARIA (Accessible Rich Internet Applications) is a set of HTML attributes that tell assistive technology what an element is, what it’s called and what state it’s in. Used well, it makes custom components understandable to screen reader users. Used badly, it makes things worse than having no ARIA at all.
Industry research has repeatedly found that pages using ARIA tend to have more detected accessibility errors than pages without it, not fewer. That isn’t because ARIA is bad. It’s because it’s often added as a quick fix without understanding what it does. This guide covers the rules that prevent most problems and ten mistakes I see regularly in audits.
Key takeaways
- The first rule of ARIA: if a native HTML element does what you need, use it instead of ARIA.
- ARIA changes what assistive technology announces. It does not add keyboard behaviour, focus or styling.
- Every ARIA role comes with obligations: required states, properties and keyboard interaction.
aria-hidden="true"on focusable content, andaria-labelthat contradicts visible text, are two of the most damaging mistakes.- Always test ARIA with a real screen reader. Validators can’t tell you whether it makes sense.
The five rules of ARIA use
The W3C’s “Using ARIA” guidance sets out five rules. In plain language:
- Use native HTML first. If there’s an element with the semantics and behaviour you need, use it.
- Don’t change native semantics unless you really have to. Don’t turn a heading into a button with
role; put a button inside the heading. - All interactive ARIA controls must be keyboard operable. If you add
role="button", you must handle Enter and Space. - Don’t hide focusable elements. Never use
role="presentation"oraria-hidden="true"on something users can focus. - Interactive elements must have an accessible name.
Most of the mistakes below are breaking one of these rules.
10 common ARIA mistakes
1. Using ARIA instead of a native element
<!-- Mistake -->
<div role="button" onclick="addToCart()">Add to cart</div>
<!-- Fix -->
<button type="button" onclick="addToCart()">Add to cart</button>The div version is announced as a button but can’t be reached with Tab and doesn’t respond to Enter or Space unless you add all of that yourself. The native button does it all for free.
2. Adding aria-label to non-interactive elements
<!-- Mistake -->
<div aria-label="Product details">...</div>
<span aria-label="Important">!</span>aria-label on a plain div or span (which have no meaningful role) is often ignored by screen readers. If the content matters, make it visible text or give the element an appropriate role, such as a section with a heading, or use visually hidden text.
3. aria-hidden="true" on focusable content
<!-- Mistake -->
<div aria-hidden="true">
<a href="/offers">See offers</a>
</div>The link is hidden from screen readers but still receives keyboard focus, so users land on something that’s silent or announced inconsistently. If content should be hidden from everyone, use hidden or display: none. If it must stay visible but inert, use the inert attribute.
4. aria-label that doesn’t match visible text
<!-- Mistake -->
<button aria-label="Submit your application form">Apply</button>The visible label is “Apply”, but the accessible name doesn’t contain it. A voice control user who says “click Apply” may get nothing. This fails WCAG 2.5.3 Label in Name. Either remove the aria-label or make sure it starts with the visible text.
5. States that never update
<button aria-expanded="false" aria-controls="menu">Menu</button>If JavaScript opens the menu but never changes aria-expanded to true, screen reader users are told the menu is collapsed when it’s open. The same problem affects aria-selected, aria-checked, aria-pressed and aria-current. States must be kept in sync with what’s on screen.
6. Using role="menu" for website navigation
role="menu" and role="menuitem" are meant for application menus, like a desktop app’s File menu. They tell screen readers to expect arrow-key navigation and they change how content is announced. A normal site navigation is better built as a nav element containing a list of links, with disclosure buttons for submenus if needed.
7. Misusing live regions
Live regions (aria-live, role="status", role="alert") announce dynamic updates. Common problems:
- Adding the live region to the page at the same time as the message, so it isn’t announced. The live region container should exist in the DOM first; then you update its content.
- Using
aria-live="assertive"orrole="alert"for routine updates, which interrupts users constantly. - Making a huge area live, so every small change reads out a whole section.
Use role="status" (polite) for things like “Item added to cart” and reserve role="alert" for urgent, time-sensitive messages.
8. Broken ID references
aria-labelledby, aria-describedby and aria-controls point to other elements by ID. If the ID is misspelled, duplicated, or the referenced element doesn’t exist (common in components rendered conditionally), the relationship silently fails. Duplicate IDs are a particular problem in reusable components rendered more than once on a page.
9. Removing semantics from meaningful content
<!-- Mistake -->
<table role="presentation"> <!-- on a real data table -->
<ul role="presentation"> <!-- on a list that is genuinely a list -->role="presentation" and role="none" strip the element’s meaning. On layout tables that’s fine; on data tables or real lists it removes information users need, like “table with 5 rows” or “list, 8 items”.
10. Redundant or conflicting roles
<button role="button">Save</button>
<nav role="navigation">...</nav>
<a href="/pricing" role="button">Pricing</a>The first two are redundant. They’re mostly harmless, but they add noise and suggest the author is guessing. The third is worse: a link that navigates announced as a button sets the wrong expectations, since users expect buttons to perform actions and links to go somewhere.
When ARIA is the right tool
ARIA is essential when HTML has no native equivalent, for example:
- Tabs:
role="tablist",role="tab",role="tabpanel",aria-selected - Disclosure widgets:
aria-expandedon the toggle button - Comboboxes and autocomplete:
role="combobox",aria-expanded,aria-activedescendant - Status messages:
role="status" - Describing extra context:
aria-describedbyfor hints and errors, as shown in my accessible forms guide
For each, follow the matching pattern in the W3C ARIA Authoring Practices Guide, including its keyboard interaction.
How to test ARIA
- Run an automated checker such as axe. It catches invalid attributes, broken references and missing names.
- Inspect the accessibility tree in Chrome or Firefox developer tools to see the computed name, role and state.
- Test with a screen reader. Listen to what is announced when you focus and operate each control. See my screen reader testing guide.
- Test with the keyboard. ARIA roles promise keyboard behaviour; make sure it’s there.
Conclusion
ARIA is a precision tool, not a coat of paint. Reach for native HTML first, add ARIA only to fill genuine gaps, keep states in sync, never hide focusable content, and test with a real screen reader. Follow those rules and ARIA will make your interfaces clearer instead of more confusing.
Further reading
- Keyboard Accessibility and Focus Management
- Audio-First Design Heuristics
- W3C: Using ARIA
- W3C: ARIA Authoring Practices Guide


Pingback: Accessible Forms Guide: Labels, Errors and Validation (WCAG 2.2)
Pingback: Keyboard Accessibility and Focus Management Guide (WCAG 2.2)