Accessibility is not a feature for a small percentage of users. It is a fundamental quality of web experiences. At least 15% of people have disabilities affecting how they interact with technology. And accessibility improvements benefit everyone: captions help in noisy environments, keyboard navigation helps power users, clear structure helps mobile users, good contrast helps in bright sunlight.
Building accessible applications requires understanding how assistive technologies work, following established guidelines, and testing with the tools disabled users rely on. It is not just about screen readers; it is about color contrast, keyboard navigation, cognitive load, and flexible interfaces that adapt to user needs.
I have built accessible interfaces, conducted accessibility audits, and worked with users of assistive technologies. I have learned that semantic HTML eliminates many accessibility problems, that ARIA should be a last resort, and that automated testing catches only a fraction of issues. This guide covers the patterns that work: understanding WCAG guidelines and compliance levels, semantic HTML as the foundation, ARIA patterns for complex components, keyboard navigation implementation, testing with assistive technologies, and building inclusive design systems.
Understanding Disability and Accessibility
Types of Disabilities
Visual: Blindness, low vision, color blindness Auditory: Deafness, hearing impairment Motor: Limited dexterity, inability to use mouse Cognitive: Learning disabilities, attention deficits, memory issues
Assistive Technologies
Screen readers: NVDA, JAWS, VoiceOver Screen magnifiers: ZoomText Switch controls: Single-button navigation Voice control: Dragon NaturallySpeaking
WCAG 2.1 Guidelines
The Four Principles (POUR)
Perceivable: Information must be presentable in ways users can perceive Operable: Interface components must be operable by all users Understandable: Information and UI operation must be understandable Robust: Content must work with current and future assistive technologies
Compliance Levels
Level A: Minimum accessibility (must have) Level AA: Strong accessibility (should have) - Legal requirement in many jurisdictions. For a step-by-step auditing process, read our website ADA and WCAG compliance guide. Level AAA: Excellent accessibility (ideal to have)
Key Requirements
Perceivable:
- Text alternatives for images
- Captions/transcripts for multimedia
- Minimum 4.5:1 contrast ratio (AA)
- Resizable text up to 200%
Operable:
- All functionality available by keyboard
- No keyboard traps
- Sufficient time to complete tasks
- No seizure-inducing content
Understandable:
- Readable text (simplified language for AAA)
- Consistent navigation
- Error prevention and recovery
- Input assistance
Robust:
- Valid HTML
- Compatible with assistive technologies
- Status messages announced
Semantic HTML
The Foundation
Semantic HTML provides accessibility for free. Use it before adding ARIA.
<!-- Bad: Generic divs -->
<div class="header">...</div>
<div class="nav">...</div>
<div class="main">
<div class="article">
<div class="h1">Title</div>
</div>
</div>
<!-- Good: Semantic elements -->
<header>...</header>
<nav>...</nav>
<main>
<article>
<h1>Title</h1>
</article>
</main>
Headings
<!-- Proper heading hierarchy -->
<h1>Page Title</h1>
<h2>Section 1</h2>
<h3>Subsection 1.1</h3>
<h2>Section 2</h2>
<h3>Subsection 2.1</h3>
<h3>Subsection 2.2</h3>
Forms
<form>
<!-- Associated label -->
<label for="email">Email Address</label>
<input
type="email"
id="email"
name="email"
required
aria-required="true"
aria-describedby="email-error"
>
<span id="email-error" role="alert" class="error"></span>
<!-- Fieldset for groups -->
<fieldset>
<legend>Preferred Contact Method</legend>
<input type="radio" id="contact-email" name="contact" value="email">
<label for="contact-email">Email</label>
<input type="radio" id="contact-phone" name="contact" value="phone">
<label for="contact-phone">Phone</label>
</fieldset>
</form>
Landmarks
<header role="banner">
<nav role="navigation" aria-label="Main">
<!-- Navigation links -->
</nav>
</header>
<main role="main">
<article>
<h1>Article Title</h1>
</article>
</main>
<aside role="complementary" aria-label="Related articles">
<!-- Related content -->
</aside>
<footer role="contentinfo">
<!-- Copyright, links -->
</footer>
ARIA Patterns
When to Use ARIA
Use when:
- Creating custom components (tabs, modals, accordions)
- Providing additional context
- Dynamic content updates
Do not use when:
- Native HTML element exists
- Semantic HTML suffices
- Adding ARIA to hide semantics
Common Patterns
Button:
<!-- Custom button -->
<div role="button"
tabindex="0"
aria-pressed="false"
onclick="handleClick()"
onkeydown="handleKeydown(event)">
Click me
</div>
Modal Dialog:
<div role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-description">
<h2 id="dialog-title">Confirm Delete</h2>
<p id="dialog-description">
This will permanently delete the item.
</p>
<button>Cancel</button>
<button>Delete</button>
</div>
Tabs:
<div class="tabs">
<div role="tablist" aria-label="Product Sections">
<button role="tab"
aria-selected="true"
aria-controls="panel-1"
id="tab-1">
Description
</button>
<button role="tab"
aria-selected="false"
aria-controls="panel-2"
id="tab-2"
tabindex="-1">
Specifications
</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
<!-- Description content -->
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
<!-- Specifications content -->
</div>
</div>
Live Regions:
<!-- Status announcements -->
<div id="status" role="status" aria-live="polite" aria-atomic="true">
<!-- Content changes announced to screen readers -->
</div>
<!-- Alert announcements -->
<div id="alert" role="alert" aria-live="assertive">
<!-- Urgent messages -->
</div>
Keyboard Navigation
Focus Management
// Visible focus indicator
:focus {
outline: 2px solid #005fcc;
outline-offset: 2px;
}
// Focus trap for modals
class Modal {
open() {
this.previousFocus = document.activeElement;
this.modal.showModal();
// Trap focus
this.modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
this.trapFocus(e);
}
});
// Focus first element
this.focusableElements[0]?.focus();
}
close() {
this.modal.close();
// Restore focus
this.previousFocus?.focus();
}
trapFocus(event) {
const focusable = this.focusableElements;
const first = focusable[0];
const last = focusable[focusable.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
}
}
Keyboard Shortcuts
// Accessible keyboard handling
document.addEventListener('keydown', (e) => {
// Skip link
if (e.key === 'Tab' && e.target === document.body) {
document.getElementById('skip-link').focus();
}
// Escape to close modals
if (e.key === 'Escape') {
closeOpenModals();
}
// Arrow keys for custom components
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
if (isInListbox(e.target)) {
navigateListbox(e);
}
}
});
Testing Accessibility
Automated Testing
Automated tools are an excellent starting point for scanning your site for common accessibility violations. For websites running on WordPress, you can leverage dedicated plugins to automate and monitor these checks; see our guide to the best WordPress plugins for ADA and WCAG compliance to choose the right solution.
For developers and custom builds, CLI and testing libraries offer programmatic validation:
# axe-core CLI
axe https://example.com --tags wcag2aa
# Pa11y
pa11y https://example.com --standard WCAG2AA
# Lighthouse
lighthouse https://example.com --only-categories=accessibility
# Jest + jest-axe
npm install --save-dev jest-axe
// jest-axe example
import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
import Button from './Button';
expect.extend(toHaveNoViolations);
test('Button has no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
Manual Testing
Keyboard-only navigation:
- Unplug mouse
- Navigate entire page with Tab, Shift+Tab, Enter, Space, Arrow keys
- Verify all functionality works
- Check focus visibility
Screen reader testing:
NVDA (Windows, free):
- Install NVDA
- Navigate with Insert + Arrow keys
- Read element: Insert + Tab
VoiceOver (macOS, built-in):
- Enable: Cmd + F5
- Navigate: Cmd + Option + Arrow keys
- Read element: Ctrl + Option + Cmd + T
Color and Contrast
# WAVE browser extension
# Check contrast errors
# axe DevTools
# Shows contrast ratios
Manual check:
- Screenshot in grayscale
- Verify all information is still conveyed
- Check that color is not the only indicator
Inclusive Design

Cognitive Accessibility
/* Readable text */
body {
font-family: system-ui, sans-serif;
font-size: 16px;
line-height: 1.5;
max-width: 70ch;
}
/* Clear focus */
:focus {
outline: 3px solid;
outline-offset: 3px;
}
/* Reduce motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Responsive and Flexible
/* Text resizing support */
html {
font-size: 100%; /* Respect user preference */
}
/* Container queries for flexibility */
.card {
container-type: inline-size;
}
@container (min-width: 400px) {
.card-content {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
/* Touch target sizing */
button, .button {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
}
Testing Checklist
## Accessibility Checklist
### Visual
- [ ] Color contrast 4.5:1 for text (AA)
- [ ] Information not conveyed by color alone
- [ ] Text resizes to 200% without loss
- [ ] Focus indicators visible
### Motor
- [ ] All functionality keyboard accessible
- [ ] No keyboard traps
- [ ] Skip links provided
- [ ] Sufficient time for interactions
### Hearing
- [ ] Captions for video
- [ ] Transcripts for audio
- [ ] Visual alternatives to audio cues
### Cognitive
- [ ] Clear, simple language
- [ ] Consistent navigation
- [ ] Error prevention and recovery
- [ ] Reduced motion support
Accessibility Culture
Team Practices
Shift-left testing:
- Accessibility in design reviews
- Component-level accessibility testing
- Automated checks in CI/CD
Documentation:
- A11y acceptance criteria in tickets
- Pattern library with accessibility notes
- Decision log for accessibility trade-offs
Legal and Business Case
Regulations:
- ADA (US)
- Section 508 (US Government)
- EN 301 549 (EU)
- AODA (Canada)
Business benefits:
- Larger addressable market
- Better SEO (semantic HTML)
- Reduced legal risk
- Improved usability for all
Conclusion
Accessibility is quality. Build it in from the start using semantic HTML. Use ARIA sparingly for custom components. Test with keyboards and screen readers. Maintain sufficient contrast and flexible layouts.
The web was designed to work for everyone. Our implementations should honor that principle. Accessible design benefits all users while being essential for some.
Further Reading
- WCAG 2.1 Specification: Official guidelines
- WAI-ARIA Authoring Practices: Component patterns
- “Inclusive Design Patterns” by Heydon Pickering
- A11y Project: Community resources
- Deque University: Training and certification