Building Accessible Web Applications: A Developer's Guide
Learn how to create inclusive web applications that work for everyone, including users with disabilities. Discover practical accessibility techniques and WCAG guidelines.
- Author
- Navinda Chandrasiri
- Published
- Reading time
- 7 min read
On this page
- Why Accessibility Matters
- The Four Principles of WCAG
- 1. Perceivable
- 2. Operable
- 3. Understandable
- 4. Robust
- Essential Accessibility Techniques
- 1. Semantic HTML
- 2. ARIA Attributes
- 3. Keyboard Navigation
- 4. Focus Management
- 5. Color Contrast
- 6. Form Accessibility
- Testing for Accessibility
- Automated Tools
- Manual Testing
- Testing Checklist
- Common Accessibility Mistakes
- 1. DIV and SPAN Overuse
- 2. Missing Alternative Text
- 3. Poor Link Text
- 4. Inaccessible Custom Components
- Conclusion
- Resources
Web accessibility isn't just about compliance - it's about creating experiences that work for everyone. Let's explore how to build truly accessible web applications.
Why Accessibility Matters#
- 1 billion people worldwide have some form of disability
- Accessibility improves UX for all users, not just those with disabilities
- Many countries have legal requirements for web accessibility
- Accessible sites often have better SEO and performance
The Four Principles of WCAG#
The Web Content Accessibility Guidelines (WCAG) are built on four principles, remembered by the acronym POUR:
1. Perceivable#
Users must be able to perceive the information being presented.
// ✅ Good: Proper image alt text
<img
src="/product.jpg"
alt="Blue running shoes with white laces"
/>
// ❌ Bad: Missing or poor alt text
<img src="/product.jpg" alt="image" />
// For decorative images
<img src="/decoration.png" alt="" role="presentation" />2. Operable#
Users must be able to operate the interface.
// ✅ Good: Keyboard accessible
<button onClick={handleClick}>
Click me
</button>
// ❌ Bad: Not keyboard accessible
<div onClick={handleClick}>
Click me
</div>3. Understandable#
Information and operation must be understandable.
// ✅ Good: Clear form labels
<label htmlFor="email">
Email Address
<input
id="email"
type="email"
aria-required="true"
/>
</label>
// ❌ Bad: No label
<input type="email" placeholder="Email" />4. Robust#
Content must be robust enough to work with various technologies.
// ✅ Good: Semantic HTML
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
// ❌ Bad: Non-semantic HTML
<div className="nav">
<div><span onClick={goHome}>Home</span></div>
</div>Essential Accessibility Techniques#
1. Semantic HTML#
Use the right HTML elements for their intended purpose:
<!-- ✅ Good: Semantic elements -->
<header>
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Article Title</h1>
<p>Content...</p>
</article>
</main>
<footer>
<p>Copyright 2024</p>
</footer>
<!-- ❌ Bad: Div soup -->
<div class="header">
<div class="nav">
<div class="link">Home</div>
</div>
</div>2. ARIA Attributes#
Use ARIA attributes to provide additional context:
// Modal dialog
<div
role="dialog"
aria-labelledby="dialog-title"
aria-describedby="dialog-description"
aria-modal="true"
>
<h2 id="dialog-title">Confirm Action</h2>
<p id="dialog-description">
Are you sure you want to proceed?
</p>
<button>Confirm</button>
<button>Cancel</button>
</div>
// Loading state
<button
disabled
aria-busy="true"
aria-label="Loading, please wait"
>
<span aria-hidden="true">⏳</span>
Loading...
</button>
// Live regions for dynamic content
<div
role="status"
aria-live="polite"
aria-atomic="true"
>
{statusMessage}
</div>3. Keyboard Navigation#
Ensure all interactive elements are keyboard accessible:
function DropdownMenu() {
const [isOpen, setIsOpen] = useState(false)
const handleKeyDown = (e: KeyboardEvent) => {
switch (e.key) {
case 'Escape':
setIsOpen(false)
break
case 'ArrowDown':
// Focus next item
break
case 'ArrowUp':
// Focus previous item
break
}
}
return (
<div>
<button
aria-expanded={isOpen}
aria-haspopup="true"
onClick={() => setIsOpen(!isOpen)}
>
Menu
</button>
{isOpen && (
<ul role="menu" onKeyDown={handleKeyDown}>
<li role="menuitem" tabIndex={0}>Item 1</li>
<li role="menuitem" tabIndex={0}>Item 2</li>
</ul>
)}
</div>
)
}4. Focus Management#
Manage focus appropriately:
function Modal({ isOpen, onClose }) {
const modalRef = useRef<HTMLDivElement>(null)
const previousFocusRef = useRef<HTMLElement>()
useEffect(() => {
if (isOpen) {
// Save current focus
previousFocusRef.current = document.activeElement as HTMLElement
// Focus modal
modalRef.current?.focus()
} else {
// Restore previous focus
previousFocusRef.current?.focus()
}
}, [isOpen])
return (
<div
ref={modalRef}
role="dialog"
aria-modal="true"
tabIndex={-1}
>
{/* Modal content */}
</div>
)
}5. Color Contrast#
Ensure sufficient color contrast:
/* ✅ Good: WCAG AA compliant (4.5:1 for normal text) */
.text {
color: #333333;
background-color: #ffffff;
}
/* ❌ Bad: Insufficient contrast */
.text {
color: #cccccc;
background-color: #ffffff;
}
/* Don't rely solely on color to convey information */
.error {
color: #dc3545;
/* Add icon or text indicator */
}6. Form Accessibility#
Create accessible forms:
function AccessibleForm() {
return (
<form aria-labelledby="form-title">
<h2 id="form-title">Contact Form</h2>
{/* Text input */}
<div>
<label htmlFor="name">
Name <span aria-label="required">*</span>
</label>
<input
id="name"
type="text"
required
aria-required="true"
aria-describedby="name-hint"
/>
<small id="name-hint">
Enter your full name
</small>
</div>
{/* Error state */}
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
/>
<div id="email-error" role="alert">
Please enter a valid email address
</div>
</div>
{/* Submit */}
<button type="submit">
Submit Form
</button>
</form>
)
}Testing for Accessibility#
Automated Tools#
- axe DevTools - Browser extension for finding accessibility issues
- Lighthouse - Built into Chrome DevTools
- WAVE - Web Accessibility Evaluation Tool
- eslint-plugin-jsx-a11y - ESLint rules for React
Manual Testing#
- Keyboard Navigation: Can you navigate without a mouse?
- Screen Reader: Test with NVDA (Windows) or VoiceOver (Mac)
- Zoom: Test at 200% zoom
- Color Blindness: Use a color blindness simulator
Testing Checklist#
- All images have appropriate alt text
- Forms have proper labels and error messages
- Interactive elements are keyboard accessible
- Color contrast meets WCAG standards
- Page structure uses semantic HTML
- Dynamic content updates are announced
- Focus is managed appropriately
- No keyboard traps exist
Common Accessibility Mistakes#
1. DIV and SPAN Overuse#
// ❌ Bad
<div onClick={handleClick}>Click me</div>
// ✅ Good
<button onClick={handleClick}>Click me</button>2. Missing Alternative Text#
// ❌ Bad
<img src="chart.png" />
// ✅ Good
<img src="chart.png" alt="Sales increased 25% in Q4" />3. Poor Link Text#
// ❌ Bad
<a href="/article">Click here</a>
// ✅ Good
<a href="/article">Read the full accessibility guide</a>4. Inaccessible Custom Components#
// ❌ Bad
<div className="checkbox" onClick={toggle}>
{checked && '✓'}
</div>
// ✅ Good
<input
type="checkbox"
checked={checked}
onChange={toggle}
aria-label="Accept terms"
/>Conclusion#
Building accessible web applications is not optional - it's a fundamental part of professional web development. By following WCAG guidelines, using semantic HTML, and testing with real users, you can create experiences that work for everyone.
Remember: Accessibility is an ongoing process, not a one-time fix. Regular audits, user testing, and staying current with best practices will help you maintain and improve accessibility over time.
Resources#
Let's build a more inclusive web, together!
Related reading
Getting Started with Vite v7: A Modern Frontend Setup Guide
An in-depth introduction to Vite v7, covering framework selection, React variants, and modern tooling choices for fast frontend development.
ViteReactFrontendTypeScript
9 min readGetting Started with Next.js 14: A Complete Guide
Learn how to build modern web applications with Next.js 14, featuring the new App Router, Server Components, and improved performance.
Next.jsReactTypeScriptWeb Development
8 min read