Accessibility Best Practices for Modern Web Apps
Accessibility (a11y) is about making web applications usable for everyone, including people with disabilities. I've learned that accessibility isn't just about compliance—it's about creating better user experiences for all users. When you design with accessibility in mind, you create interfaces that work better for everyone.
Why accessibility matters
Beyond legal requirements and helping people with disabilities, accessibility improves the user experience for everyone:
- Better SEO: Search engines can better understand your content
- Mobile users: Accessible sites often work better on mobile devices
- Older users: As we age, many of us experience temporary or permanent impairments
- Situational limitations: Poor lighting, noisy environments, or using devices one-handed
WCAG guidelines
The Web Content Accessibility Guidelines (WCAG) provide the foundation. Focus on these principles:
- Perceivable: Information and user interface components must be presentable to users in ways they can perceive
- Operable: User interface components and navigation must be operable
- Understandable: Information and the operation of user interface must be understandable
- Robust: Content must be robust enough to be interpreted reliably by a wide variety of user agents
Semantic HTML
Start with proper semantic HTML structure:
<!-- Good: Semantic headings -->
<h1>Main page heading</h1>
<h2>Section heading</h2>
<h3>Subsection heading</h3>
<!-- Good: Semantic form elements -->
<form>
<fieldset>
<legend>Contact Information</legend>
<label for="name">Name:</label>
<input type="text" id="name" name="name">
<label for="email">Email:</label>
<input type="email" id="email" name="email">
</fieldset>
</form>
<!-- Good: Semantic navigation -->
<nav>
<ul>
<li><a href="/home">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>ARIA attributes
Use ARIA attributes when semantic HTML isn't enough:
<!-- Custom component with ARIA -->
<div role="tablist">
<button
role="tab"
aria-selected="true"
aria-controls="panel1"
id="tab1"
>
Tab 1
</button>
<div
role="tabpanel"
aria-labelledby="tab1"
id="panel1"
>
Content for tab 1
</div>
</div>
<!-- Progress indicator -->
<div role="progressbar"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100">
75% complete
</div>Keyboard navigation
Ensure all interactive elements work with keyboard:
/* Make focus visible */
*:focus {
outline: 2px solid #007acc;
outline-offset: 2px;
}
/* Skip links for screen readers */
.skip-link {
position: absolute;
top: -40px;
left: 6px;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
}
.skip-link:focus {
top: 6px;
}// Custom keyboard handling
const handleKeyDown = (event) => {
switch(event.key) {
case 'Enter':
case ' ':
// Activate button
handleClick();
break;
case 'Escape':
// Close modal
closeModal();
break;
case 'ArrowDown':
// Move focus to next item
focusNextItem();
break;
}
};Color and contrast
Ensure sufficient color contrast:
/* Good contrast ratios */
.text-primary {
color: #1a1a1a; /* High contrast on white background */
}
.text-secondary {
color: #666666; /* Still readable */
}
/* Don't rely on color alone */
.error-message {
color: #d32f2f;
font-weight: bold;
}
/* Use icons or text in addition to color */
.status-success::before {
content: "✓";
color: #2e7d32;
}Images and media
Make images and media accessible:
<!-- Descriptive alt text --> <img src="team-photo.jpg" alt="Our development team posing together at the annual conference"> <!-- Decorative images --> <img src="divider.png" alt="" role="presentation"> <!-- Complex images --> <img src="chart.png" alt="Bar chart showing user growth: January 1,000 users, February 1,500 users, March 2,200 users"> <!-- Video with captions --> <video controls> <source src="demo.mp4" type="video/mp4"> <track kind="captions" src="captions.vtt" srclang="en" label="English"> </video>
Forms and validation
Create accessible forms:
<form>
<div>
<label for="email">Email Address *</label>
<input
type="email"
id="email"
name="email"
required
aria-describedby="email-help email-error"
>
<span id="email-help">We'll never share your email with anyone else.</span>
<span id="email-error" class="error" role="alert">Please enter a valid email address.</span>
</div>
<button type="submit">Subscribe</button>
</form>JavaScript considerations
Make dynamic content accessible:
// Announce dynamic content changes
const announceChange = (message) => {
const announcement = document.createElement('div');
announcement.setAttribute('aria-live', 'polite');
announcement.setAttribute('aria-atomic', 'true');
announcement.style.position = 'absolute';
announcement.style.left = '-10000px';
announcement.style.width = '1px';
announcement.style.height = '1px';
announcement.style.overflow = 'hidden';
document.body.appendChild(announcement);
announcement.textContent = message;
setTimeout(() => {
document.body.removeChild(announcement);
}, 1000);
};
// Update loading states
const showLoading = (button) => {
button.setAttribute('aria-disabled', 'true');
button.textContent = 'Loading...';
announceChange('Loading, please wait.');
};Testing accessibility
Regular testing is crucial:
Automated testing:
# Install accessibility testing tools npm install --save-dev axe-core jest-axe # Run accessibility tests npx jest --testPathPattern=accessibility
import { axe, toHaveNoViolations } from 'jest-axe';
import { render } from '@testing-library/react';
expect.extend(toHaveNoViolations);
test('Homepage is accessible', async () => {
const { container } = render(<HomePage />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});Manual testing checklist:
- Navigate with keyboard only (Tab, Enter, Space, Arrow keys)
- Test with screen reader (NVDA, JAWS, VoiceOver)
- Check color contrast with tools like WAVE or Lighthouse
- Test with browser zoom up to 200%
- Verify content makes sense when CSS is disabled
- Test form validation and error messages
Common accessibility mistakes
Avoid these pitfalls:
- Missing alt text on images
- Poor color contrast
- Missing form labels
- Inaccessible custom components
- No keyboard navigation support
- Missing focus indicators
- Auto-playing media without controls
- Inaccessible PDFs and documents
Performance and accessibility
Accessibility and performance go hand in hand:
// Lazy load images with proper alt text
import { useState, useRef, useEffect } from 'react';
function LazyImage({ src, alt, ...props }) {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
imgRef.current.src = src;
setIsLoaded(true);
observer.disconnect();
}
}
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, [src]);
return (
<img
ref={imgRef}
alt={alt}
{...props}
aria-hidden={!isLoaded}
/>
);
}Inclusive design principles
Design for everyone from the start:
- Progressive enhancement: Start with accessible HTML, add enhancements
- Mobile-first: Mobile users often benefit from accessibility features
- User testing: Include people with disabilities in user testing
- Empathy: Consider diverse user needs and situations
Legal considerations
Accessibility isn't just good practice—it's often legally required:
- WCAG 2.1 AA: Minimum standard for many organizations
- Section 508: US government accessibility requirements
- ADA compliance: US businesses serving the public
- EN 301 549: European accessibility requirements
Tools and resources
Essential accessibility tools:
- Lighthouse: Built-in accessibility auditing
- WAVE: Web accessibility evaluation tool
- axe DevTools: Browser extension for testing
- NVDA: Free screen reader for Windows
- VoiceOver: Built-in screen reader for macOS/iOS
My accessibility journey
I started thinking about accessibility as an afterthought, but now it's part of my development process from the beginning. The more accessible my sites are, the better they work for everyone.
Start small: focus on semantic HTML, proper form labels, and keyboard navigation. Use automated tools to catch issues early. Remember that accessibility is about people—design with empathy and you'll create better experiences for everyone.
Accessibility is not a checkbox to tick off. It's an ongoing commitment to inclusive design. The web should work for everyone, and that's worth investing in.
Related articles