general2/1/20247 min read
Web Accessibility Guide
Comprehensive guide to building accessible web applications that work for everyone
accessibilitya11ywcaginclusive-designsemantic-html
By Zen Frontend
Web Accessibility Guide
Web accessibility ensures that websites and applications are usable by people with disabilities. It's not just a legal requirement—it's a fundamental aspect of good user experience design.
What is Web Accessibility?
Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. This includes:
- Visual disabilities: Blindness, low vision, color blindness
- Motor disabilities: Limited fine motor control, paralysis
- Cognitive disabilities: Learning disabilities, attention disorders
- Auditory disabilities: Deafness, hard of hearing
The Four Principles of Accessibility (POUR)
1. Perceivable
Information and UI components must be presentable in ways users can perceive.
2. Operable
UI components and navigation must be operable by all users.
3. Understandable
Information and UI operation must be understandable.
4. Robust
Content must be robust enough to be interpreted by assistive technologies.
Semantic HTML
Use Proper HTML Elements
<!-- ❌ Bad - Non-semantic -->
<div class="button" onclick="submit()">Submit</div>
<div class="heading">Main Title</div>
<!-- ✅ Good - Semantic -->
<button onclick="submit()">Submit</button>
<h1>Main Title</h1>
Headings Structure
<!-- ✅ Good - Proper heading hierarchy -->
<h1>Page Title</h1>
<h2>Section Title</h2>
<h3>Subsection Title</h3>
<h3>Another Subsection</h3>
<h2>Another Section</h2>
<h3>Subsection Title</h3>
Lists and Navigation
<!-- ✅ Good - Proper list structure -->
<nav aria-label="Main navigation">
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<!-- ✅ Good - Description lists -->
<dl>
<dt>Name</dt>
<dd>John Doe</dd>
<dt>Email</dt>
<dd>john@example.com</dd>
</dl>
ARIA (Accessible Rich Internet Applications)
ARIA Labels and Descriptions
<!-- ✅ Good - ARIA labels -->
<button aria-label="Close dialog">×</button>
<input type="text" aria-label="Search products" placeholder="Search..." />
<!-- ✅ Good - ARIA descriptions -->
<input
type="password"
aria-describedby="password-help"
aria-required="true"
/>
<div id="password-help">
Password must be at least 8 characters long
</div>
ARIA States and Properties
<!-- ✅ Good - ARIA states -->
<button aria-expanded="false" aria-controls="menu">
Menu
</button>
<ul id="menu" aria-hidden="true">
<li><a href="/item1">Item 1</a></li>
<li><a href="/item2">Item 2</a></li>
</ul>
<!-- ✅ Good - ARIA properties -->
<div role="tablist" aria-label="Product features">
<button
role="tab"
aria-selected="true"
aria-controls="panel1"
id="tab1"
>
Features
</button>
<div
role="tabpanel"
aria-labelledby="tab1"
id="panel1"
>
Product features content
</div>
</div>
Live Regions
<!-- ✅ Good - Live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
<!-- Screen readers will announce changes here -->
</div>
<div aria-live="assertive" aria-atomic="false">
<!-- Urgent updates announced immediately -->
</div>
Keyboard Navigation
Focus Management
/* ✅ Good - Visible focus indicators */
button:focus,
input:focus,
a:focus {
outline: 2px solid #007bff;
outline-offset: 2px;
}
/* Custom focus styles */
.custom-button:focus {
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);
}
Tab Order
<!-- ✅ Good - Logical tab order -->
<form>
<label for="name">Name:</label>
<input type="text" id="name" />
<label for="email">Email:</label>
<input type="email" id="email" />
<button type="submit">Submit</button>
</form>
Skip Links
<!-- ✅ Good - Skip to main content -->
<a href="#main-content" class="skip-link">
Skip to main content
</a>
<main id="main-content">
<!-- Main content here -->
</main>
.skip-link {
position: absolute;
top: -40px;
left: 6px;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 1000;
}
.skip-link:focus {
top: 6px;
}
Color and Contrast
Color Contrast Ratios
- Normal text: 4.5:1 minimum
- Large text: 3:1 minimum
- UI components: 3:1 minimum
/* ✅ Good - Sufficient contrast */
.text-primary {
color: #000000; /* Black on white = 21:1 */
background-color: #ffffff;
}
.text-secondary {
color: #333333; /* Dark gray on white = 12.6:1 */
background-color: #ffffff;
}
Don't Rely on Color Alone
<!-- ❌ Bad - Color only -->
<span style="color: red;">Error: Invalid input</span>
<!-- ✅ Good - Color + icon + text -->
<span style="color: red;" aria-label="Error">
<span aria-hidden="true">⚠️</span>
Error: Invalid input
</span>
Images and Media
Alt Text
<!-- ✅ Good - Descriptive alt text -->
<img
src="chart.jpg"
alt="Bar chart showing sales increased 25% from Q1 to Q2"
/>
<!-- ✅ Good - Decorative images -->
<img src="decoration.jpg" alt="" role="presentation" />
<!-- ✅ Good - Complex images -->
<img src="infographic.jpg" alt="Sales infographic" />
<p>
<a href="infographic-text.html">Read the text version of this infographic</a>
</p>
Video and Audio
<!-- ✅ Good - Video with captions -->
<video controls>
<source src="video.mp4" type="video/mp4">
<track
kind="captions"
src="captions.vtt"
srclang="en"
label="English"
default
>
<p>Your browser doesn't support video.
<a href="video.mp4">Download the video</a>
</p>
</video>
<!-- ✅ Good - Audio with transcript -->
<audio controls>
<source src="audio.mp3" type="audio/mpeg">
<p>Your browser doesn't support audio.
<a href="transcript.html">Read the transcript</a>
</p>
</audio>
Forms and Inputs
Labels and Descriptions
<!-- ✅ Good - Proper form structure -->
<form>
<fieldset>
<legend>Contact Information</legend>
<div>
<label for="name">Full Name *</label>
<input
type="text"
id="name"
name="name"
required
aria-describedby="name-help"
/>
<div id="name-help">Enter your first and last name</div>
</div>
<div>
<label for="email">Email Address *</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-error"
/>
<div id="email-error" role="alert" aria-live="polite">
<!-- Error messages appear here -->
</div>
</div>
</fieldset>
<button type="submit">Submit</button>
</form>
Error Handling
// ✅ Good - Accessible error handling
function validateForm() {
const email = document.getElementById('email');
const errorDiv = document.getElementById('email-error');
if (!email.validity.valid) {
email.setAttribute('aria-invalid', 'true');
errorDiv.textContent = 'Please enter a valid email address';
errorDiv.setAttribute('role', 'alert');
} else {
email.setAttribute('aria-invalid', 'false');
errorDiv.textContent = '';
}
}
Testing Accessibility
Automated Testing
// Using axe-core
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
test('should not have accessibility violations', async () => {
const { container } = render(<MyComponent />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Manual Testing
-
Keyboard Navigation
- Tab through all interactive elements
- Use Enter/Space to activate buttons
- Use arrow keys for menus and lists
-
Screen Reader Testing
- Test with NVDA (Windows)
- Test with VoiceOver (macOS)
- Test with JAWS (Windows)
-
Color and Contrast
- Use browser dev tools
- Test with color blindness simulators
- Verify contrast ratios
Browser DevTools
// Check for accessibility issues
// Chrome DevTools > Lighthouse > Accessibility
// Test keyboard navigation
// Tab through the page and verify focus indicators
// Test with reduced motion
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Common Accessibility Patterns
Modal Dialogs
<!-- ✅ Good - Accessible modal -->
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
aria-describedby="modal-description"
>
<h2 id="modal-title">Confirm Action</h2>
<p id="modal-description">
Are you sure you want to delete this item?
</p>
<button onclick="closeModal()">Cancel</button>
<button onclick="confirmAction()">Delete</button>
</div>
Tabs
<!-- ✅ Good - Accessible tabs -->
<div role="tablist" aria-label="Product information">
<button
role="tab"
aria-selected="true"
aria-controls="panel1"
id="tab1"
tabindex="0"
>
Description
</button>
<button
role="tab"
aria-selected="false"
aria-controls="panel2"
id="tab2"
tabindex="-1"
>
Specifications
</button>
</div>
<div
role="tabpanel"
aria-labelledby="tab1"
id="panel1"
>
Product description content
</div>
Key Concepts
- Semantic HTML: Use proper HTML elements for structure
- ARIA: Enhance accessibility with ARIA attributes
- Keyboard Navigation: Ensure all functionality is keyboard accessible
- Color and Contrast: Meet WCAG contrast requirements
- Testing: Use both automated and manual testing methods
Common Interview Questions
- What are the four principles of accessibility?
- How do you make a website keyboard accessible?
- What's the difference between aria-label and aria-labelledby?
- How do you test for accessibility issues?
- What are the WCAG contrast requirements?
Related Topics
- Web Vitals and Performance
- Semantic HTML
- Performance Optimization
- User Experience Design
- SEO Best Practices