Introduction to Web Accessibility
Introduction to Web Accessibility
Web accessibility ensures that websites and applications are usable by everyone, including people with disabilities. It's not just about compliance—it's about creating inclusive experiences that benefit all users.
Why Accessibility Matters
Accessibility impacts everyone:
- 1 in 4 adults have some form of disability
- Legal requirements: ADA, Section 508, European Accessibility Act
- Better UX for all: Accessible sites are easier to use
- SEO benefits: Accessibility improvements often boost SEO
- Larger audience: Don't exclude potential users
Understanding WCAG Guidelines
The Web Content Accessibility Guidelines (WCAG) provide the foundation for accessibility:
Four Main Principles (POUR)
- Perceivable: Information must be presentable in different ways
- Operable: Interface components must be operable
- Understandable: Information and UI operation must be understandable
- Robust: Content must work with various assistive technologies
Conformance Levels
- Level A: Basic accessibility features
- Level AA: Removes major barriers (recommended target)
- Level AAA: Highest level (often impractical for entire sites)
Semantic HTML
The foundation of accessibility is proper HTML:
Use Semantic Elements
<!-- Bad -->
<div class="header">
<div class="nav">
<span onclick="navigate()">Home</span>
</div>
</div>
<!-- Good -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
Heading Hierarchy
<main>
<h1>Page Title</h1>
<section>
<h2>Section Title</h2>
<h3>Subsection</h3>
<h3>Another Subsection</h3>
</section>
<section>
<h2>Another Section</h2>
</section>
</main>
Keyboard Navigation
Ensure everything is keyboard accessible:
Focus Management
/* Visible focus indicators */
:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Skip to main content link */
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
position: absolute;
left: 6px;
top: 6px;
z-index: 999;
}
Tab Order
<!-- Use tabindex sparingly -->
<button>First (natural order)</button>
<button>Second (natural order)</button>
<button tabindex="-1">Not tabbable</button>
<button tabindex="0">Tabbable with natural order</button>
Keyboard Traps
// Trap focus in modal
function trapFocus(element) {
const focusableElements = element.querySelectorAll(
'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
const firstFocusable = focusableElements[0];
const lastFocusable = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstFocusable) {
e.preventDefault();
lastFocusable.focus();
} else if (!e.shiftKey && document.activeElement === lastFocusable) {
e.preventDefault();
firstFocusable.focus();
}
}
});
}
ARIA (Accessible Rich Internet Applications)
ARIA helps make complex UI patterns accessible:
ARIA Rules
- Don't use ARIA if you can use semantic HTML
- Don't change native semantics unnecessarily
- All interactive elements must be keyboard accessible
- Don't hide focusable elements
- Provide accessible names for all elements
Common ARIA Patterns
<!-- Accessible button -->
<button
aria-label="Close dialog"
aria-pressed="false"
onclick="toggleDialog()"
>
<svg><!-- icon --></svg>
</button>
<!-- Live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
<p>Form saved successfully!</p>
</div>
<!-- Describing relationships -->
<input
id="email"
type="email"
aria-describedby="email-error"
required
/>
<span id="email-error" role="alert">
Please enter a valid email
</span>
Forms and Inputs
Accessible forms are crucial for user interaction:
Label Association
<!-- Explicit labels -->
<label for="username">Username</label>
<input id="username" type="text" required />
<!-- Implicit labels -->
<label>
Password
<input type="password" required />
</label>
<!-- Multiple descriptions -->
<label for="phone">Phone Number</label>
<input
id="phone"
type="tel"
aria-describedby="phone-format phone-error"
/>
<span id="phone-format">Format: (123) 456-7890</span>
<span id="phone-error" role="alert">Invalid phone number</span>
Error Handling
function AccessibleForm() {
const [errors, setErrors] = useState({});
return (
<form aria-label="Contact form">
<div role="group">
<label htmlFor="email">
Email <span aria-label="required">*</span>
</label>
<input
id="email"
type="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? "email-error" : undefined}
/>
{errors.email && (
<span id="email-error" role="alert">
{errors.email}
</span>
)}
</div>
</form>
);
}
Images and Media
Make visual content accessible:
Alternative Text
<!-- Informative images -->
<img
src="chart.png"
alt="Sales increased 25% from 2023 to 2024"
/>
<!-- Decorative images -->
<img src="decoration.png" alt="" role="presentation" />
<!-- Complex images -->
<figure>
<img
src="complex-diagram.png"
alt="System architecture diagram"
aria-describedby="diagram-desc"
/>
<figcaption id="diagram-desc">
The system consists of three layers: presentation,
business logic, and data storage...
</figcaption>
</figure>
Video Accessibility
<video controls>
<source src="video.mp4" type="video/mp4" />
<track
kind="captions"
src="captions.vtt"
srclang="en"
label="English"
/>
<track
kind="descriptions"
src="descriptions.vtt"
srclang="en"
label="English Audio Descriptions"
/>
</video>
Color and Contrast
Ensure sufficient color contrast:
Contrast Requirements
- Normal text: 4.5:1 ratio
- Large text (18pt+): 3:1 ratio
- UI components: 3:1 ratio
/* Good contrast examples */
.text-primary {
color: #1a1a1a; /* on white: 21:1 */
background: #ffffff;
}
.text-secondary {
color: #666666; /* on white: 5.74:1 */
background: #ffffff;
}
/* Don't rely on color alone */
.error {
color: #d32f2f;
font-weight: bold;
}
.error::before {
content: "⚠ "; /* Icon for non-color indicator */
}
Testing for Accessibility
Automated Testing
// React with react-axe
import axe from '@axe-core/react';
if (process.env.NODE_ENV === 'development') {
axe(React, ReactDOM, 1000);
}
Manual Testing Checklist
- [ ] Keyboard only: Navigate without mouse
- [ ] Screen reader: Test with NVDA/JAWS/VoiceOver
- [ ] Color contrast: Check with tools
- [ ] Zoom to 200%: Content should reflow
- [ ] Disable CSS: Content should make sense
- [ ] Check headings: Logical hierarchy
Tools
- axe DevTools: Browser extension
- WAVE: Web accessibility evaluation
- Lighthouse: Built into Chrome
- Screen readers: NVDA (free), JAWS, VoiceOver
- Keyboard navigation tester: Browser built-in
Common Accessibility Patterns
Accessible Modal
function AccessibleModal({ isOpen, onClose, title, children }) {
useEffect(() => {
if (isOpen) {
// Save last focused element
const lastFocused = document.activeElement;
// Focus modal
modalRef.current?.focus();
return () => {
// Restore focus on close
lastFocused?.focus();
};
}
}, [isOpen]);
if (!isOpen) return null;
return (
<div
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
ref={modalRef}
tabIndex={-1}
>
<h2 id="modal-title">{title}</h2>
<button
onClick={onClose}
aria-label="Close modal"
>
×
</button>
{children}
</div>
);
}
Accessible Navigation
<nav aria-label="Main navigation">
<ul>
<li>
<a href="/" aria-current="page">Home</a>
</li>
<li>
<a href="/about">About</a>
</li>
<li>
<button
aria-expanded="false"
aria-controls="products-menu"
>
Products
</button>
<ul id="products-menu" hidden>
<li><a href="/software">Software</a></li>
<li><a href="/hardware">Hardware</a></li>
</ul>
</li>
</ul>
</nav>
Conclusion
Web accessibility is not optional—it's essential for creating inclusive web experiences. Start with semantic HTML, ensure keyboard accessibility, provide appropriate ARIA labels, and test with real assistive technologies.
Remember: accessibility is not a feature to add later, but a fundamental aspect of good web development. By following these guidelines, you'll create better experiences for all users, not just those with disabilities.
Make the web accessible to everyone—it's the right thing to do, and it makes business sense too.