Accessibility in Web Development
Web accessibility (often abbreviated as a11y) ensures that websites and web applications are usable by everyone, including people with disabilities. This includes users who are blind, deaf, have motor impairments, cognitive disabilities, or use assistive technologies. Building accessible websites isn't just a nice-to-have—it's a legal requirement in many countries and simply the right thing to do.
An accessible web benefits everyone. Curb cuts designed for wheelchairs help parents with strollers. Captions help people in noisy environments. Good color contrast helps users in bright sunlight. When we design for accessibility, we create a better experience for all users.
- Understanding WCAG guidelines and conformance levels
- Writing semantic HTML for screen readers
- Implementing ARIA attributes correctly
- Creating accessible forms and error handling
- Keyboard navigation and focus management
- Color contrast and visual accessibility
- Testing tools and automated accessibility checks
- Common accessibility mistakes to avoid
Understanding Web Accessibility
Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can perceive, understand, navigate, interact with, and contribute to the Web. Web accessibility encompasses all disabilities that affect access to the Web, including auditory, cognitive, neurological, physical, speech, and visual disabilities.
The POUR principles form the foundation of WCAG guidelines
WCAG Guidelines and Conformance Levels
The Web Content Accessibility Guidelines (WCAG) are the international standard for web accessibility. Currently at version 2.2, WCAG provides three conformance levels: A, AA, and AAA. Most organizations aim for WCAG 2.1 Level AA compliance, which is also required by many accessibility laws.
| Level | Description | Requirements | Legal Standard |
|---|---|---|---|
| Level A | Minimum accessibility | 30 success criteria | Basic compliance |
| Level AA | Acceptable accessibility | 20 additional criteria | Most laws require this |
| Level AAA | Optimal accessibility | 28 additional criteria | Aspirational goal |
While AAA is the highest level, it's not always achievable for all content. Focus on meeting AA requirements, then enhance with AAA criteria where practical. Even simple improvements like better color contrast and keyboard navigation make a significant difference.
Semantic HTML: The Foundation
Semantic HTML is the cornerstone of web accessibility. Using the correct HTML elements conveys meaning and structure to assistive technologies. Screen readers rely on semantic markup to announce content appropriately and enable navigation.
Common Semantic Elements
Semantic HTML elements provide structure and meaning for assistive technologies
<!-- ✅ Semantic HTML - Accessible -->
<header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Understanding Web Accessibility</h1>
<p>Web accessibility ensures everyone can use the web...</p>
<section>
<h2>Why It Matters</h2>
<p>Over 1 billion people have disabilities...</p>
</section>
</article>
<aside aria-label="Related content">
<h2>Related Articles</h2>
<ul>
<li><a href="/wcag">WCAG Guidelines</a></li>
</ul>
</aside>
</main>
<footer>
<p>© 2026 My Website</p>
</footer>
<!-- ❌ Non-Semantic HTML - Not Accessible -->
<div class="header">
<div class="nav">
<div class="nav-item">
<span onclick="goTo('/')">Home</span>
</div>
<div class="nav-item">
<span onclick="goTo('/about')">About</span>
</div>
</div>
</div>
<div class="main">
<div class="article">
<div class="title">Understanding Web Accessibility</div>
<div class="text">Web accessibility ensures...</div>
<div class="section">
<div class="subtitle">Why It Matters</div>
<div class="text">Over 1 billion people...</div>
</div>
</div>
<div class="sidebar">
<div class="sidebar-title">Related Articles</div>
<div class="sidebar-item">
<span onclick="goTo('/wcag')">WCAG Guidelines</span>
</div>
</div>
</div>
<div class="footer">
<div>© 2026 My Website</div>
</div>
Using <div> and <span> for everything might look the same visually, but screen readers can't understand the structure. They won't announce headings, navigation landmarks, or button roles. Always use the appropriate semantic element.
ARIA: Accessible Rich Internet Applications
ARIA (Accessible Rich Internet Applications) provides additional attributes that enhance accessibility when native HTML isn't sufficient. However, the first rule of ARIA is: don't use ARIA if you can use native HTML.
ARIA Roles, States, and Properties
Roles
Define what an element is: role="button", role="alert", role="dialog"
States
Current condition: aria-expanded, aria-checked, aria-selected
Properties
Characteristics: aria-label, aria-describedby, aria-required
Live Regions
Dynamic updates: aria-live="polite", aria-live="assertive"
<!-- Custom button with ARIA -->
<div role="button"
tabindex="0"
aria-pressed="false"
onclick="toggleButton(this)"
onkeydown="handleKeyPress(event, this)">
Toggle Feature
</div>
<!-- Accordion with ARIA -->
<button aria-expanded="false"
aria-controls="panel1"
id="accordion1">
Section Title
</button>
<div id="panel1"
role="region"
aria-labelledby="accordion1"
hidden>
Panel content goes here...
</div>
<!-- Live region for dynamic updates -->
<div aria-live="polite" aria-atomic="true" class="sr-only">
<!-- Screen readers announce changes here -->
</div>
<!-- Modal dialog -->
<div role="dialog"
aria-modal="true"
aria-labelledby="dialog-title"
aria-describedby="dialog-desc">
<h2 id="dialog-title">Confirm Action</h2>
<p id="dialog-desc">Are you sure you want to proceed?</p>
<button>Confirm</button>
<button>Cancel</button>
</div>
Keyboard Navigation
Many users rely solely on keyboards to navigate websites. This includes people with motor disabilities, blind users, and power users who prefer keyboard shortcuts. Every interactive element must be keyboard accessible.
Understanding keyboard navigation patterns
// Skip link for keyboard users
const skipLink = document.querySelector('.skip-link');
skipLink.addEventListener('click', (e) => {
e.preventDefault();
const main = document.querySelector('main');
main.tabIndex = -1;
main.focus();
});
// Focus trap for modals
function trapFocus(element) {
const focusableElements = element.querySelectorAll(
'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
if (e.key === 'Escape') {
closeModal();
}
});
firstElement.focus();
}
// Custom keyboard handler for interactive elements
function handleKeyboard(event, callback) {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
callback();
}
}
Accessible Forms
Forms are often the most challenging aspect of accessibility. Users need to understand what information is required, receive clear error messages, and be able to correct mistakes easily.
<form novalidate>
<!-- Properly labeled input -->
<div class="form-group">
<label for="email">
Email Address
<span aria-hidden="true">*</span>
</label>
<input
type="email"
id="email"
name="email"
aria-required="true"
aria-describedby="email-hint email-error"
autocomplete="email"
>
<span id="email-hint" class="hint">
We'll never share your email
</span>
<span id="email-error" class="error" role="alert" hidden>
Please enter a valid email address
</span>
</div>
<!-- Radio group with fieldset -->
<fieldset>
<legend>Preferred Contact Method</legend>
<div>
<input type="radio" id="contact-email" name="contact" value="email">
<label for="contact-email">Email</label>
</div>
<div>
<input type="radio" id="contact-phone" name="contact" value="phone">
<label for="contact-phone">Phone</label>
</div>
</fieldset>
<!-- Accessible error summary -->
<div role="alert" aria-live="assertive" id="error-summary" hidden>
<h2>There were errors with your submission</h2>
<ul>
<!-- Errors listed here with links to fields -->
</ul>
</div>
<button type="submit">Submit Form</button>
</form>
Form Accessibility Checklist
<label> with matching for/id
aria-required)
role="alert" and are linked via aria-describedby
<fieldset> and <legend> for grouped inputs
autocomplete attributes for common fields
Color Contrast and Visual Design
Color contrast is crucial for users with low vision or color blindness. WCAG requires specific contrast ratios between text and background colors to ensure readability.
Contrast Examples
This text passes WCAG AA
This text fails WCAG
Images and Media Accessibility
All non-text content needs text alternatives. This includes images, videos, audio, and interactive content. The type of alternative depends on the purpose of the content.
Informative Images
Images that convey information need descriptive alt text.
<!-- Good: Descriptive alt text -->
<img
src="chart-sales-q4.png"
alt="Bar chart showing Q4 sales: Product A $50k, Product B $75k, Product C $30k"
>
<!-- Good: Action-oriented for functional images -->
<img
src="search-icon.png"
alt="Search"
>
<!-- Bad: Non-descriptive -->
<img src="chart.png" alt="chart">
<img src="image1.jpg" alt="image">
Decorative Images
Images that don't add information should be hidden from screen readers.
<!-- Option 1: Empty alt (recommended) -->
<img src="decorative-border.png" alt="">
<!-- Option 2: Using role -->
<img src="background-pattern.png" alt="" role="presentation">
<!-- Option 3: CSS background (best for decoration) -->
<div class="hero" style="background-image: url('hero-bg.jpg')">
<h1>Welcome</h1>
</div>
Complex Images
Charts, diagrams, and infographics need extended descriptions.
<figure>
<img
src="org-chart.png"
alt="Company organizational chart"
aria-describedby="org-chart-desc"
>
<figcaption>
Company Structure as of 2026
</figcaption>
</figure>
<div id="org-chart-desc" class="sr-only">
The CEO reports to the Board of Directors.
Three VPs report to the CEO: VP of Engineering (manages 50 developers),
VP of Sales (manages 30 sales staff), and VP of Marketing (manages 15 marketers).
</div>
Video Accessibility
Videos need captions, transcripts, and audio descriptions.
<video controls>
<source src="tutorial.mp4" type="video/mp4">
<!-- Captions for deaf/hard of hearing -->
<track
kind="captions"
src="captions-en.vtt"
srclang="en"
label="English"
default
>
<!-- Audio descriptions for blind users -->
<track
kind="descriptions"
src="descriptions-en.vtt"
srclang="en"
label="English Audio Description"
>
Your browser doesn't support video.
<a href="tutorial-transcript.html">Read transcript</a>
</video>
<!-- Link to full transcript -->
<p><a href="tutorial-transcript.html">Full video transcript</a></p>
Testing for Accessibility
Accessibility testing should be integrated throughout the development process, not just at the end. Use a combination of automated tools, manual testing, and real user testing with assistive technologies.
Automated Testing Tools
Automated tools catch about 30-40% of accessibility issues. Use them as a first pass, but don't rely on them exclusively.
- axe DevTools - Browser extension, most popular
- WAVE - Visual feedback on page
- Lighthouse - Built into Chrome DevTools
- eslint-plugin-jsx-a11y - React linting rules
- Pa11y - CI/CD integration
// Automated testing with Jest and axe-core
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);
describe('Accessibility Tests', () => {
it('should have no accessibility violations', async () => {
const { container } = render(<MyComponent />);
const results = await axe(container);
expect(results).toHaveNoViolations();
});
});
// Cypress with axe-core
describe('Page Accessibility', () => {
beforeEach(() => {
cy.visit('/');
cy.injectAxe();
});
it('Has no detectable a11y violations', () => {
cy.checkA11y();
});
it('Checks specific element', () => {
cy.checkA11y('.main-content', {
rules: {
'color-contrast': { enabled: true }
}
});
});
});
// Pa11y in CI/CD
// package.json script
{
"scripts": {
"test:a11y": "pa11y-ci --config .pa11yci.json"
}
}
// .pa11yci.json
{
"defaults": {
"standard": "WCAG2AA",
"timeout": 5000
},
"urls": [
"http://localhost:3000/",
"http://localhost:3000/about",
"http://localhost:3000/contact"
]
}
Manual Testing Checklist
Keyboard Navigation
Navigate your entire site using only Tab, Shift+Tab, Enter, Space, and Arrow keys. Every interactive element should be reachable and usable.
Screen Reader Testing
Test with NVDA (Windows, free), VoiceOver (Mac/iOS, built-in), or JAWS. Listen to how your content is announced.
Zoom and Magnification
Zoom to 200% and 400%. Content should reflow without horizontal scrolling. No information should be cut off.
Color and Contrast
Test with color blindness simulators. View in grayscale. Check contrast ratios with tools like the WebAIM Contrast Checker.
Common Accessibility Mistakes
❌ Missing or Poor Alt Text
Problem: Images without alt text, or with useless alt like "image" or the filename.
Solution: Write descriptive alt text that conveys the same information as the image. Use empty alt for decorative images.
❌ Removing Focus Outlines
Problem: Using outline: none or :focus { outline: 0 } makes keyboard navigation impossible.
Solution: If you don't like the default outline, replace it with a custom visible focus indicator. Use :focus-visible to only show focus for keyboard users.
❌ Inaccessible Custom Components
Problem: Building custom dropdowns, modals, or tabs without proper ARIA, keyboard support, or focus management.
Solution: Use established accessible component libraries, or follow WAI-ARIA Authoring Practices. Test with screen readers.
❌ Auto-Playing Media
Problem: Videos or audio that play automatically can disorient users, interfere with screen readers, and cause distress.
Solution: Never auto-play media with sound. If video must auto-play, keep it muted with easy controls to pause/stop.
Accessibility isn't just good practice—it's the law in many jurisdictions:
- ADA (USA) - Applies to places of public accommodation, including websites
- Section 508 (USA) - Federal agencies must be accessible
- EAA (EU) - European Accessibility Act covers digital products
- AODA (Canada) - Ontario accessibility requirements
Non-compliance can result in lawsuits, fines, and reputation damage.
Getting Started Checklist
Quick Wins for Accessibility
- Add descriptive
alttext to all informative images - Ensure all form inputs have associated labels
- Don't remove focus outlines—style them instead
- Use semantic HTML elements (
<button>,<nav>,<main>) - Check color contrast ratios (4.5:1 for normal text)
- Add a skip-to-content link at the top of pages
- Test keyboard navigation through your entire site
- Run automated tests with axe or Lighthouse
- Add captions to all videos
- Test with a screen reader at least once
"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect."
— Tim Berners-Lee, W3C Director and inventor of the World Wide Web
Web accessibility is not a feature—it's a fundamental aspect of good web development. By building with accessibility in mind from the start, you create better experiences for everyone. Start small, test often, and continuously improve. The web should be for everyone.
