Building Accessible Web Applications: A Developer's Guide
Accessibility isn't optional—it's a requirement. Building accessible applications means building applications that everyone can use, including people with disabilities. After making applications accessible and seeing the impact, I've learned that accessibility benefits everyone, not just people with disabilities.
Why Accessibility Matters
Legal Requirements
- ADA (Americans with Disabilities Act) - Legal requirement in US
- WCAG (Web Content Accessibility Guidelines) - International standard
- Section 508 - US federal requirements
Business Benefits
- Larger audience - 15% of world population has disabilities
- Better SEO - Accessible sites rank better
- Better UX - Accessible sites are easier to use for everyone
- Legal compliance - Avoid lawsuits
WCAG Principles
1. Perceivable
Information must be perceivable to users.
2. Operable
Interface components must be operable.
3. Understandable
Information and UI must be understandable.
4. Robust
Content must be robust enough for assistive technologies.
Semantic HTML
Use Proper Elements
<!-- Bad: Divs for everything -->
<div onclick="submit()">Submit</div>
<!-- Good: Semantic HTML -->
<button type="submit">Submit</button>
Common Semantic Elements
<!-- Headings -->
<h1>Main Title</h1>
<h2>Section Title</h2>
<!-- Navigation -->
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<!-- Main content -->
<main>
<article>
<h1>Article Title</h1>
<p>Content...</p>
</article>
</main>
<!-- Forms -->
<form>
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<button type="submit">Submit</button>
</form>
ARIA Attributes
When to Use ARIA
Use ARIA when HTML isn't enough:
<!-- Bad: Custom button without ARIA -->
<div class="button" onclick="handleClick()">Click me</div>
<!-- Good: Use button -->
<button onclick="handleClick()">Click me</button>
<!-- When you must use div, add ARIA -->
<div
role="button"
tabindex="0"
onclick="handleClick()"
onkeydown="handleKeyDown(event)"
>
Click me
</div>
Common ARIA Attributes
<!-- Labels -->
<button aria-label="Close dialog">×</button>
<!-- Descriptions -->
<input
type="text"
aria-describedby="email-help"
/>
<span id="email-help">Enter your email address</span>
<!-- States -->
<button aria-expanded="false" aria-controls="menu">
Menu
</button>
<div id="menu" aria-hidden="true">
<!-- Menu items -->
</div>
<!-- Live regions -->
<div aria-live="polite" aria-atomic="true">
<!-- Dynamic content updates -->
</div>
Keyboard Navigation
Focus Management
// Ensure all interactive elements are keyboard accessible
function handleKeyDown(event) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleClick();
}
}
// Manage focus
function openModal() {
modal.show();
// Focus first interactive element
modal.querySelector('input').focus();
}
function closeModal() {
modal.hide();
// Return focus to trigger
triggerButton.focus();
}
Tab Order
<!-- Logical tab order -->
<button>First</button>
<button>Second</button>
<button>Third</button>
<!-- Skip links -->
<a href="#main-content" class="skip-link">Skip to main content</a>
Forms
Proper Labels
<!-- Bad: No label -->
<input type="text" name="email" />
<!-- Good: Explicit label -->
<label for="email">Email</label>
<input type="email" id="email" name="email" />
<!-- Good: Implicit label -->
<label>
Email
<input type="email" name="email" />
</label>
Error Messages
<!-- Associate errors with inputs -->
<label for="email">Email</label>
<input
type="email"
id="email"
name="email"
aria-invalid="true"
aria-describedby="email-error"
/>
<span id="email-error" role="alert">
Please enter a valid email address
</span>
Required Fields
<label for="name">
Name
<span aria-label="required">*</span>
</label>
<input
type="text"
id="name"
name="name"
required
aria-required="true"
/>
Images
Alt Text
<!-- Decorative image -->
<img src="decoration.jpg" alt="" />
<!-- Informative image -->
<img
src="chart.jpg"
alt="Sales increased 25% from Q1 to Q2"
/>
<!-- Complex image -->
<img
src="diagram.jpg"
alt=""
aria-describedby="diagram-description"
/>
<p id="diagram-description">
System architecture showing three tiers: frontend, API, database
</p>
Color and Contrast
Contrast Ratios
- Normal text: 4.5:1 minimum
- Large text: 3:1 minimum
- UI components: 3:1 minimum
/* Good: High contrast */
.button {
background: #0066cc;
color: #ffffff; /* Contrast: 7:1 */
}
/* Bad: Low contrast */
.button {
background: #cccccc;
color: #ffffff; /* Contrast: 1.6:1 - fails */
}
Don't Rely on Color Alone
<!-- Bad: Color only -->
<span style="color: red">Error</span>
<!-- Good: Color + icon/text -->
<span style="color: red" aria-label="Error">
<span aria-hidden="true">⚠️</span> Error: Invalid input
</span>
Focus Indicators
Visible Focus
/* Good: Visible focus */
button:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Bad: Remove focus */
button:focus {
outline: none; /* Don't do this! */
}
Screen Readers
Testing with Screen Readers
- NVDA (Windows, free)
- JAWS (Windows, paid)
- VoiceOver (macOS/iOS, built-in)
- TalkBack (Android, built-in)
Screen Reader Best Practices
<!-- Use headings for structure -->
<h1>Page Title</h1>
<h2>Section Title</h2>
<!-- Use landmarks -->
<nav aria-label="Main navigation">
<!-- Navigation -->
</nav>
<main>
<!-- Main content -->
</main>
<aside aria-label="Related articles">
<!-- Sidebar -->
</aside>
Common Accessibility Issues
1. Missing Alt Text
<!-- Bad -->
<img src="photo.jpg" />
<!-- Good -->
<img src="photo.jpg" alt="Team meeting in conference room" />
2. Poor Color Contrast
/* Bad */
.text { color: #999999; } /* Low contrast */
/* Good */
.text { color: #333333; } /* High contrast */
3. Missing Labels
<!-- Bad -->
<input type="text" name="email" />
<!-- Good -->
<label for="email">Email</label>
<input type="email" id="email" name="email" />
4. Keyboard Inaccessible
<!-- Bad -->
<div onclick="handleClick()">Click me</div>
<!-- Good -->
<button onclick="handleClick()">Click me</button>
Accessibility Testing
Automated Testing
// axe-core
const axe = require('axe-core');
async function testAccessibility() {
const results = await axe.run();
console.log(results.violations);
}
Manual Testing
- Keyboard navigation - Tab through entire page
- Screen reader - Test with NVDA/VoiceOver
- Color contrast - Use contrast checker
- Zoom - Test at 200% zoom
Real-World Example
Challenge: E-commerce site not accessible, losing customers.
Improvements Made:
- Semantic HTML - Replaced divs with proper elements
- ARIA labels - Added where needed
- Keyboard navigation - All features keyboard accessible
- Color contrast - Improved to WCAG AA
- Alt text - All images have descriptive alt text
- Form labels - All inputs properly labeled
Result:
- Accessibility score: 45 → 95
- More users able to complete purchases
- Better SEO rankings
- Legal compliance
Best Practices
- Start early - Build accessibility in from the start
- Use semantic HTML - Let HTML do its job
- Test regularly - Automated and manual testing
- Keyboard navigation - Everything should be keyboard accessible
- Color contrast - Meet WCAG standards
- Screen reader testing - Test with actual screen readers
- Document patterns - Share accessible patterns with team
- Continuous improvement - Accessibility is ongoing
Conclusion
Accessibility isn't a feature—it's a requirement. Building accessible applications means:
- More users - Reach everyone
- Better UX - Easier for all users
- Legal compliance - Avoid issues
- Better code - Semantic, maintainable
Remember: Accessibility benefits everyone. A site that's easy to navigate with a keyboard is easier for everyone. Good contrast helps everyone read. Clear labels help everyone understand.
What accessibility challenges have you faced? What improvements have you made to your applications?
Related Posts
REST API Design Best Practices: Building Developer-Friendly APIs
Learn how to design REST APIs that are intuitive, maintainable, and developer-friendly. From URL structure to error handling, master the principles that make APIs great.
AI Security and Privacy: Building Trustworthy AI Applications
Understand critical security and privacy considerations when building AI applications. Learn about prompt injection attacks, data privacy regulations, model safety, and how to build AI systems users can trust.
The Art of Code Refactoring: When and How to Refactor Legacy Code
Learn the art and science of refactoring legacy code. Discover when to refactor, how to do it safely, and techniques that have transformed unmaintainable codebases.
Documentation Best Practices: Writing Code That Documents Itself
Learn how to write effective documentation that helps your team understand and maintain code. From code comments to API docs, master the art of clear communication.
Code Review Best Practices: How to Review Code Effectively
Master the art of code review. Learn how to provide constructive feedback, catch bugs early, and improve code quality through effective peer review.
Docker and Containerization: Best Practices for Production
Master Docker containerization with production-ready best practices. Learn how to build efficient, secure, and maintainable containerized applications.