Accessibility isn't optional—it's essential. Let's master WCAG guidelines and create inclusive web experiences.
What is WCAG?
WCAG (Web Content Accessibility Guidelines) are standards for making web content accessible to people with disabilities.
WCAG 2.1 Principles (POUR)
- 1Perceivable: Users must perceive content
- 2Operable: Users must operate interface
- 3Understandable: Users must understand content
- 4Robust: Content must work with assistive tech
Level Conformance
- A: Minimum accessibility
- AA: Standard compliance (recommended)
- AAA: Highest accessibility (difficult)
Perceivable
1. Text Alternatives
All images need alt text:
<!-- Decorative -->
<img src="decorative.png" alt="" role="presentation">
<!-- Informative -->
<img src="chart.png" alt="Bar chart showing 50% increase">
<!-- Complex -->
<img src="complex.png" alt="Detailed description">
<p class="sr-only">Full description in text</p>2. Captions for Media
<video controls>
<source src="video.mp4" type="video/mp4">
<track kind="captions" src="captions.vtt" srclang="en" label="English">
</video>3. Contrast Ratios
Minimum contrast requirements:
- Normal text: 4.5:1
- Large text (18pt+): 3:1
- UI components: 3:1
/* Good contrast */
.text {
color: #1a1a1a; /* Dark gray on white = 16.5:1 */
background: #ffffff;
}
/* Bad contrast */
.text {
color: #999999; /* Light gray on white = 2.8:1 */
background: #ffffff;
}Test with: WebAIM Contrast Checker
4. Resizable Text
/* Allow 200% zoom without breaking */
html {
font-size: 100%; /* Base size */
}
body {
/* Use relative units */
font-size: 1rem;
line-height: 1.5;
}
/* Avoid fixed widths */
.container {
max-width: 1200px;
width: 100%; /* Fluid */
}Operable
1. Keyboard Accessible
All interactive elements must work with keyboard:
<!-- Bad: No keyboard access -->
<div onclick="doSomething()">Click me</div>
<!-- Good: Accessible -->
<button onclick="doSomething()">Click me</button>
<!-- Custom element with keyboard -->
<div
role="button"
tabindex="0"
onkeydown="if (event.key === 'Enter') doSomething()"
onclick="doSomething()"
>
Click me
</div>2. Focus Indicators
Visible focus for keyboard navigation:
/* Remove default outline */
*:focus {
outline: none;
}
/* Add visible focus indicator */
*:focus-visible {
outline: 3px solid #0066cc;
outline-offset: 2px;
}
/* Skip to content link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #0066cc;
color: white;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}3. Sufficient Time
Provide enough time to read and interact:
// Allow users to disable timeouts
const timeout = setTimeout(() => {
// Auto-action
}, 5000);
// Provide disable option
const disableTimeout = checkbox.checked;
if (disableTimeout) {
clearTimeout(timeout);
}4. Seizure Prevention
Don't flash more than 3 times per second:
.animation {
/* Avoid fast flashing */
animation: flash 0.5s infinite; /* Bad! */
}
@keyframes flash {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}Understandable
1. Language Declaration
<html lang="en">
<head>
<title>Page Title</title>
</head>
</html>2. Consistent Navigation
Keep navigation consistent across pages:
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>3. Error Identification
Clear error messages:
<form>
<label for="email">Email:</label>
<input
id="email"
type="email"
required
aria-invalid="false"
aria-describedby="email-error"
>
<span id="email-error" role="alert" class="error">
Please enter a valid email address
</span>
</form>4. Instructions and Labels
<form>
<fieldset>
<legend>Choose your plan:</legend>
<input id="basic" name="plan" type="radio" value="basic">
<label for="basic">Basic Plan - $9/month</label>
<input id="pro" name="plan" type="radio" value="pro">
<label for="pro">Pro Plan - $19/month</label>
</fieldset>
</form>Robust
1. ARIA Attributes
Enhance accessibility with ARIA:
<!-- Landmarks -->
<header role="banner">
<nav aria-label="Main">...</nav>
</header>
<main role="main">
<article aria-labelledby="article-title">
<h1 id="article-title">Article Title</h1>
</article>
</main>
<aside aria-label="Sidebar">...</aside>
<footer role="contentinfo">...</footer>
<!-- Live regions -->
<div role="status" aria-live="polite">
Form submitted successfully
</div>
<div role="alert" aria-live="assertive">
Error occurred
</div>
<!-- Dynamic content -->
<div role="region" aria-live="polite" aria-atomic="true">
Loading your data...
</div>2. Semantic HTML
Use proper HTML elements:
<!-- Bad -->
<div class="button">Click me</div>
<div class="header">Title</div>
<div class="nav">Links</div>
<!-- Good -->
<button>Click me</button>
<h1>Title</h1>
<nav><a href="/">Link</a></nav>Testing Accessibility
Automated Testing
Lighthouse:
# Chrome DevTools
# Lighthouse > Accessibilityaxe DevTools:
# Chrome Extension
# Scan entire pageWAVE:
# Browser extension
# Visual accessibility feedbackManual Testing
Keyboard Navigation:
- 1Tab through entire page
- 2Verify focus order is logical
- 3All interactive elements reachable
- 4Focus indicators visible
Screen Reader:
- 1NVDA (Windows) - Free
- 2JAWS (Windows) - Paid
- 3VoiceOver (Mac) - Built-in
- 4TalkBack (Android) - Built-in
Color Contrast:
- 1Use WebAIM Contrast Checker
- 2Test with grayscale
- 3Test with color blindness simulators
Testing Checklist
- [ ] All images have alt text
- [ ] Color contrast meets WCAG AA
- [ ] Keyboard navigation works
- [ ] Focus indicators visible
- [ ] Form fields have labels
- [ ] Error messages clear
- [ ] ARIA attributes correct
- [ ] Semantic HTML used
- [ ] Skip links provided
- [ ] Captions on videos
- [ ] Resizable to 200%
- [ ] Works with screen reader
Common Mistakes
1. Missing Alt Text
<!-- Bad -->
<img src="photo.jpg">
<!-- Good -->
<img src="photo.jpg" alt="Sunset over mountains">2. Poor Focus Styles
/* Bad */
*:focus {
outline: none;
}
/* Good */
*:focus-visible {
outline: 3px solid currentColor;
outline-offset: 2px;
}3. Insufficient Color Contrast
/* Bad */
.text {
color: #cccccc; /* Too light */
background: #ffffff;
}
/* Good */
.text {
color: #333333; /* Sufficient contrast */
background: #ffffff;
}4. Missing Form Labels
<!-- Bad -->
<input type="email" placeholder="Email">
<!-- Good -->
<label for="email">Email:</label>
<input id="email" type="email">Benefits of Accessibility
- 1Larger Audience: 15% of world population has disabilities
- 2Better SEO: Search engines favor accessible sites
- 3Legal Compliance: ADA, Section 508 requirements
- 4Better UX: Accessibility helps everyone
- 5Future-Proof: Aging population needs accessible sites
Conclusion
Accessibility is not optional—it's essential. Start with semantic HTML, ensure keyboard navigation, provide text alternatives, and test thoroughly. Use automated tools but don't rely on them—manual testing with assistive technologies is crucial.
Build for everyone, not just the average user. Accessibility makes the web better for all of us.