Antipatterns
Common accessibility mistakes and how to fix them.
Contents
- Div soup
- ARIA overuse
- Placeholder as label
- Empty or meaningless alt text
- Removing focus indicators
- Mouse-only interactions
- Auto-playing media
- Ignoring motion preferences
- Color as only indicator
Div soup
Wrong:
<div class="nav">
<div class="nav-item" onclick="goto('/')">Home</div>
<div class="nav-item" onclick="goto('/about')">About</div>
</div>
Right:
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
The div version requires role="navigation", role="link", tabindex="0", keyboard handlers, and focus styles. The semantic version works out of the box.
ARIA overuse
Wrong:
<button role="button" aria-label="Submit form" tabindex="0">
Submit form
</button>
Right:
<button type="submit">Submit form</button>
Problems with the wrong version:
role="button"is redundant on<button>aria-labelduplicates visible text (use only when no visible text)tabindex="0"is redundant on<button>
Rule: If a native HTML element does what you need, use it without ARIA.
Placeholder as label
Wrong:
<input type="email" placeholder="Email address">
Right:
<label for="email">Email address</label>
<input type="email" id="email" placeholder="e.g. user@example.com">
Problems with placeholder-only:
- Disappears when typing (users forget what field is for)
- Low contrast by default (hard to read)
- Not announced as field name by all screen readers
- Placeholder is hint text, not label
Empty or meaningless alt text
Wrong:
<img src="graph.png" alt="">
<img src="graph.png" alt="image">
<img src="graph.png" alt="graph.png">
Right:
<img src="graph.png" alt="Line graph showing revenue growth from $1M to $3M over 2024">
alt=""marks image as decorative - only use for truly decorative images- "image", "photo", "graph" are meaningless - describe the content
- Filenames are not descriptions
For complex images: Provide brief alt + detailed description nearby:
<figure>
<img src="flowchart.png" alt="Application deployment workflow">
<figcaption>
<details>
<summary>Flowchart description</summary>
<p>1. Developer pushes to main branch. 2. CI runs tests...</p>
</details>
</figcaption>
</figure>
Removing focus indicators
Wrong:
*:focus {
outline: none;
}
button:focus {
outline: 0;
}
Right:
button:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
/* If you must customize, provide visible alternative */
button:focus-visible {
outline: none;
box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
}
Focus indicators tell keyboard users where they are. Removing them makes the site unusable for keyboard navigation.
Mouse-only interactions
Wrong:
<div
onMouseEnter={() => setOpen(true)}
onMouseLeave={() => setOpen(false)}
>
<span>Hover for menu</span>
{open && <Menu />}
</div>
Right:
<button
onClick={() => setOpen(!open)}
onKeyDown={(e) => e.key === 'Escape' && setOpen(false)}
aria-expanded={open}
aria-haspopup="menu"
>
Menu
</button>
{open && <Menu onClose={() => setOpen(false)} />}
- Hover doesn't exist on touch devices
- Keyboard users can't trigger hover
- Click/keyboard activation works universally
Auto-playing media
Wrong:
<video autoplay>
<source src="intro.mp4" type="video/mp4">
</video>
Right:
<video controls>
<source src="intro.mp4" type="video/mp4">
<track kind="captions" src="intro.vtt" srclang="en" label="English">
</video>
- Auto-play disrupts screen readers
- Users with vestibular disorders need motion control
- Background audio conflicts with screen reader audio
- Always provide captions for video with speech
If auto-play is required:
<video autoplay muted>
And provide visible pause control.
Ignoring motion preferences
Wrong:
.hero {
animation: parallax 2s infinite;
}
.button {
transition: all 0.5s ease;
}
Right:
.hero {
animation: parallax 2s infinite;
}
.button {
transition: all 0.5s ease;
}
@media (prefers-reduced-motion: reduce) {
.hero {
animation: none;
}
.button {
transition: none;
}
}
- Animations can trigger vestibular disorders (vertigo, nausea)
- ~35% of adults over 40 have vestibular dysfunction
- Check
prefers-reduced-motionfor: parallax, transitions, auto-playing animations, smooth scrolling - "Reduce" doesn't mean "remove all" - subtle opacity fades are usually fine
Color as only indicator
Wrong:
<span class="status-good">Available</span> <!-- green -->
<span class="status-bad">Unavailable</span> <!-- red -->
Right:
<span class="status-good">✓ Available</span>
<span class="status-bad">✗ Unavailable</span>
<!-- Or with icons -->
<span class="status-good">
<svg aria-hidden="true"><!-- checkmark --></svg>
Available
</span>
- ~8% of men have color vision deficiency
- Color alone doesn't convey meaning to screen readers
- Use text, icons, or patterns in addition to color