Why Accessibility Often Gets Skipped
Accessibility work gets pushed to the end of projects because it's invisible to most developers during development. There's no lint error when you forget an alt attribute. There's no build failure when your color contrast is too low. You have to actively check.
That's exactly why I build these habits into my process rather than treating them as a final audit step.
Semantic HTML First
The single highest-leverage accessibility improvement is using semantic HTML elements. Screen readers, browsers, and assistive technology have built-in support for semantic elements — you get accessible behavior for free.
<!-- ❌ Div soup — no semantics, no keyboard navigation, no accessibility -->
<div class="nav">
<div class="nav-item" onclick="navigate()">Home</div>
</div>
<div class="article">
<div class="title">My Post</div>
</div>
<!-- ✅ Semantic — screen readers understand the structure -->
<nav>
<a href="/">Home</a>
</nav>
<article>
<h1>My Post</h1>
</article>Use <button> for actions, <a> for navigation. Use <nav>, <main>, <aside>, <header>, <footer> as landmark regions. Use heading levels (h1–h6) to create a logical document outline — don't skip levels.
Keyboard Navigation
Every interactive element must be operable with a keyboard alone. Tab to focus, Enter/Space to activate, Escape to close modals.
Rules:
- Interactive elements (
<a>,<button>,<input>) are focusable by default. Don't use<div>with an onClick for things users need to interact with. - Never use
outline: noneoroutline: 0without providing a visible alternative focus style. - Modals and drawers need focus trapping — Tab should cycle within the modal, not behind it.
- When a modal closes, return focus to the element that opened it.
// Focus ring example — don't remove it, style it
// ❌ Bad
button:focus { outline: none; }
// ✅ Good — visible for keyboard users, hidden for mouse
button:focus-visible {
outline: 2px solid #3b82f6;
outline-offset: 2px;
}Color Contrast
WCAG AA requires a contrast ratio of at least 4.5:1 for normal text and 3:1 for large text (18px+ or 14px+ bold). Check your palette at [contrast.tools](https://contrast.tools) or using browser DevTools.
Common failures:
- Light gray text on white backgrounds.
- Colored text (blue links, green success) that relies on color alone to convey meaning.
- Placeholder text (almost always too light — treat it like real text).
Images: Alt Text That Means Something
<!-- ❌ Missing — screen reader says "image" -->
<img src="chart.png">
<!-- ❌ Useless — screen reader says "image image" -->
<img src="chart.png" alt="image">
<!-- ✅ Descriptive — communicates the content -->
<img src="revenue-chart.png" alt="Bar chart showing 40% revenue growth from Q1 to Q3 2025">
<!-- ✅ Decorative images get empty alt — screen reader skips them -->
<img src="decorative-divider.png" alt="">Ask: "If this image didn't load, what information would a user miss?" That's what the alt text should convey.
ARIA: Use Sparingly
ARIA (Accessible Rich Internet Applications) attributes extend HTML semantics, but the first rule of ARIA is: don't use ARIA if a native HTML element will do. ARIA overrides semantic HTML — misusing it makes things worse, not better.
Common valid uses:
<!-- Describe the current state -->
<button aria-expanded="true" aria-controls="menu">Menu</button>
<!-- Label an element that has no visible text label -->
<button aria-label="Close dialog">✕</button>
<!-- Associate a form field with its error message -->
<input aria-describedby="email-error" />
<p id="email-error" role="alert">Please enter a valid email</p>
<!-- Live regions for dynamic content -->
<div aria-live="polite">Showing 24 of 150 results</div>Forms: The Most Common Failure Area
<!-- ❌ No association between label and input -->
<span>Email</span>
<input type="text" placeholder="Enter email">
<!-- ✅ Explicit association — clicking the label focuses the input -->
<label for="email">Email</label>
<input id="email" type="email" autocomplete="email">
<!-- ✅ Error messages associated with the field -->
<label for="email">Email</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<p id="email-error" role="alert">Please enter a valid email address</p>Testing Your Work
Automated tools catch ~30% of accessibility issues. Manual testing catches the rest:
1. Keyboard test: Unplug your mouse. Navigate your entire page with Tab, Shift+Tab, Enter, Space, and arrow keys. Can you reach everything?
2. Screen reader test: On Mac, enable VoiceOver (Cmd+F5) and navigate. On Windows, use NVDA (free) or Narrator. Does the page make sense?
3. Browser DevTools: Chrome DevTools → Accessibility panel shows the accessibility tree and flags issues.
4. axe DevTools: Browser extension from Deque. Free tier catches the most common violations.
The goal isn't a perfect score on an automated audit — it's a site that real people with real assistive technology can actually use.
