The first rule of ARIA, straight from the W3C: don't use ARIA if a native HTML element does the job. Native elements ship with keyboard behavior, focus management, and semantics that ARIA can only describe, never create. With that rule in hand, here are the five antipatterns we see in nearly every codebase we scan.
1. The fake button
<!-- ✗ Announced as nothing. Unreachable by keyboard. -->
<div class="btn" onClick={submit}>Submit</div>
<!-- ✓ Free keyboard support, focus, semantics -->
<button type="button" onClick={submit}>Submit</button>
The div version fails three WCAG criteria at once: 2.1.1 (not keyboard-operable), 4.1.2 (no role, no accessible name as a control), and usually 2.4.7 (no visible focus). Patching it with role="button" and tabindex="0" still leaves you implementing Enter and Space key handling by hand. The <button> element is one tag and zero bugs.
2. aria-label that silences visible content
<!-- ✗ Screen reader users hear "Products" — sighted users see more -->
<a href="/products" aria-label="Products">
Products <span class="count">(12 new)</span>
</a>
aria-label doesn't add to an element's name — it replaces it. Whatever text is inside becomes invisible to assistive technology. This also breaks voice-control users, who say "click Products 12 new" and nothing matches (WCAG 2.5.3, Label in Name). Use aria-label only for elements with no visible text, like icon buttons — and even there, prefer visually-hidden text or aria-labelledby pointing at real content.
3. Redundant and contradictory roles
<!-- ✗ Redundant: nav already has role navigation -->
<nav role="navigation">…</nav>
<!-- ✗✗ Contradictory: now it's a button that's secretly a heading -->
<h2 role="button" onclick="toggle()">Details</h2>
Redundant roles are mostly noise (and an easy lint win). Contradictory roles are destructive: role="button" on an <h2> removes the heading from the document outline, so screen reader users navigating by headings simply never find that section. If a heading toggles content, put a real <button> inside it:
<h2><button aria-expanded="false" onclick="toggle()">Details</button></h2>
4. Decorative noise reaching the screen reader
<!-- ✗ Announced as "star star star star star" or worse -->
<span class="stars">★★★★★</span>
<!-- ✓ One meaningful announcement -->
<span class="stars" aria-hidden="true">★★★★★</span>
<span class="visually-hidden">Rated 5 out of 5</span>
Icon fonts, emoji used as bullets, duplicate link text next to a thumbnail — every one of these gets read aloud unless you hide it. The pattern is always the same: aria-hidden="true" on the decoration, real text (visible or visually hidden) carrying the meaning. The inverse bug is worse: aria-hidden="true" on a container that has focusable elements inside, which creates ghost tab stops that announce nothing.
5. Live-region spam
<!-- ✗ Politely interrupts the user… on every keystroke -->
<div aria-live="assertive">
{searchResults.length} results
</div>
aria-live="assertive" interrupts whatever the user is currently reading. Wired to a search-as-you-type results count, it makes the page unusable: every keystroke cancels the previous announcement. Rules of thumb:
- Default to
aria-live="polite". Reserveassertivefor genuine urgency (session expiry, destructive errors). - Debounce announcements that follow typing.
- The live region must exist in the DOM before the update — injecting a new live region with text already in it often announces nothing.
- Use
role="status"for results counts and toasts; it's polite by default and semantically right.
The meta-mistake
All five patterns share a root cause: ARIA applied to make markup look accessible rather than verified to behave accessibly. The fix is mechanical checking plus occasional listening: run a screen reader over your critical flows once per release. Thirty minutes with VoiceOver teaches more than any style guide.
Catch bad ARIA before it merges
Accessibility Compliance Helper Pro flags redundant roles, broken ARIA references, name-and-role failures, and more — in HTML, JSX, and TSX, with one-click fixes.
Install from JetBrains Marketplace