antipatterns.md

  1# Antipatterns
  2
  3Common accessibility mistakes and how to fix them.
  4
  5## Contents
  6- [Div soup](#div-soup)
  7- [ARIA overuse](#aria-overuse)
  8- [Placeholder as label](#placeholder-as-label)
  9- [Empty or meaningless alt text](#empty-or-meaningless-alt-text)
 10- [Removing focus indicators](#removing-focus-indicators)
 11- [Mouse-only interactions](#mouse-only-interactions)
 12- [Auto-playing media](#auto-playing-media)
 13- [Ignoring motion preferences](#ignoring-motion-preferences)
 14- [Color as only indicator](#color-as-only-indicator)
 15
 16## Div soup
 17
 18**Wrong:**
 19```html
 20<div class="nav">
 21  <div class="nav-item" onclick="goto('/')">Home</div>
 22  <div class="nav-item" onclick="goto('/about')">About</div>
 23</div>
 24```
 25
 26**Right:**
 27```html
 28<nav>
 29  <a href="/">Home</a>
 30  <a href="/about">About</a>
 31</nav>
 32```
 33
 34The div version requires `role="navigation"`, `role="link"`, `tabindex="0"`, keyboard handlers, and focus styles. The semantic version works out of the box.
 35
 36## ARIA overuse
 37
 38**Wrong:**
 39```html
 40<button role="button" aria-label="Submit form" tabindex="0">
 41  Submit form
 42</button>
 43```
 44
 45**Right:**
 46```html
 47<button type="submit">Submit form</button>
 48```
 49
 50Problems with the wrong version:
 51- `role="button"` is redundant on `<button>`
 52- `aria-label` duplicates visible text (use only when no visible text)
 53- `tabindex="0"` is redundant on `<button>`
 54
 55**Rule:** If a native HTML element does what you need, use it without ARIA.
 56
 57## Placeholder as label
 58
 59**Wrong:**
 60```html
 61<input type="email" placeholder="Email address">
 62```
 63
 64**Right:**
 65```html
 66<label for="email">Email address</label>
 67<input type="email" id="email" placeholder="e.g. user@example.com">
 68```
 69
 70Problems with placeholder-only:
 71- Disappears when typing (users forget what field is for)
 72- Low contrast by default (hard to read)
 73- Not announced as field name by all screen readers
 74- Placeholder is hint text, not label
 75
 76## Empty or meaningless alt text
 77
 78**Wrong:**
 79```html
 80<img src="graph.png" alt="">
 81<img src="graph.png" alt="image">
 82<img src="graph.png" alt="graph.png">
 83```
 84
 85**Right:**
 86```html
 87<img src="graph.png" alt="Line graph showing revenue growth from $1M to $3M over 2024">
 88```
 89
 90- `alt=""` marks image as decorative - only use for truly decorative images
 91- "image", "photo", "graph" are meaningless - describe the content
 92- Filenames are not descriptions
 93
 94**For complex images:** Provide brief alt + detailed description nearby:
 95```html
 96<figure>
 97  <img src="flowchart.png" alt="Application deployment workflow">
 98  <figcaption>
 99    <details>
100      <summary>Flowchart description</summary>
101      <p>1. Developer pushes to main branch. 2. CI runs tests...</p>
102    </details>
103  </figcaption>
104</figure>
105```
106
107## Removing focus indicators
108
109**Wrong:**
110```css
111*:focus {
112  outline: none;
113}
114
115button:focus {
116  outline: 0;
117}
118```
119
120**Right:**
121```css
122button:focus {
123  outline: 2px solid #005fcc;
124  outline-offset: 2px;
125}
126
127/* If you must customize, provide visible alternative */
128button:focus-visible {
129  outline: none;
130  box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5);
131}
132```
133
134Focus indicators tell keyboard users where they are. Removing them makes the site unusable for keyboard navigation.
135
136## Mouse-only interactions
137
138**Wrong:**
139```jsx
140<div 
141  onMouseEnter={() => setOpen(true)}
142  onMouseLeave={() => setOpen(false)}
143>
144  <span>Hover for menu</span>
145  {open && <Menu />}
146</div>
147```
148
149**Right:**
150```jsx
151<button
152  onClick={() => setOpen(!open)}
153  onKeyDown={(e) => e.key === 'Escape' && setOpen(false)}
154  aria-expanded={open}
155  aria-haspopup="menu"
156>
157  Menu
158</button>
159{open && <Menu onClose={() => setOpen(false)} />}
160```
161
162- Hover doesn't exist on touch devices
163- Keyboard users can't trigger hover
164- Click/keyboard activation works universally
165
166## Auto-playing media
167
168**Wrong:**
169```html
170<video autoplay>
171  <source src="intro.mp4" type="video/mp4">
172</video>
173```
174
175**Right:**
176```html
177<video controls>
178  <source src="intro.mp4" type="video/mp4">
179  <track kind="captions" src="intro.vtt" srclang="en" label="English">
180</video>
181```
182
183- Auto-play disrupts screen readers
184- Users with vestibular disorders need motion control
185- Background audio conflicts with screen reader audio
186- Always provide captions for video with speech
187
188If auto-play is required:
189```html
190<video autoplay muted>
191```
192And provide visible pause control.
193
194## Ignoring motion preferences
195
196**Wrong:**
197```css
198.hero {
199  animation: parallax 2s infinite;
200}
201
202.button {
203  transition: all 0.5s ease;
204}
205```
206
207**Right:**
208```css
209.hero {
210  animation: parallax 2s infinite;
211}
212
213.button {
214  transition: all 0.5s ease;
215}
216
217@media (prefers-reduced-motion: reduce) {
218  .hero {
219    animation: none;
220  }
221  
222  .button {
223    transition: none;
224  }
225}
226```
227
228- Animations can trigger vestibular disorders (vertigo, nausea)
229- ~35% of adults over 40 have vestibular dysfunction
230- Check `prefers-reduced-motion` for: parallax, transitions, auto-playing animations, smooth scrolling
231- "Reduce" doesn't mean "remove all" - subtle opacity fades are usually fine
232
233## Color as only indicator
234
235**Wrong:**
236```html
237<span class="status-good">Available</span>  <!-- green -->
238<span class="status-bad">Unavailable</span> <!-- red -->
239```
240
241**Right:**
242```html
243<span class="status-good">✓ Available</span>
244<span class="status-bad">✗ Unavailable</span>
245
246<!-- Or with icons -->
247<span class="status-good">
248  <svg aria-hidden="true"><!-- checkmark --></svg>
249  Available
250</span>
251```
252
253- ~8% of men have color vision deficiency
254- Color alone doesn't convey meaning to screen readers
255- Use text, icons, or patterns in addition to color