Accessibility isn’t an afterthought or a nice-to-have—it’s fundamental to the web. This tutorial covers WCAG guidelines, ARIA attributes, and testing strategies to ensure your sites work for everyone.
What you’ll learn:
WCAG 2.1 guidelines and conformance levels
ARIA (Accessible Rich Internet Applications)
Keyboard navigation and focus management
Screen reader optimization
Color contrast and visual accessibility
Testing with assistive technology
Common accessibility patterns
Prerequisites:
WCAG (Web Content Accessibility Guidelines) is the international standard for web accessibility. It organizes requirements into four principles:
Users must be able to perceive content. Provide alternatives for non-text content:
<!-- Image with alt text -->
< img src = " chart.jpg " alt = " Sales growth 2020-2024: from $10k to $50k " >
<!-- Video with captions -->
< track kind = " captions " src = " captions.vtt " >
<!-- Audio with transcript -->
< audio controls src = " podcast.mp3 " ></ audio >
< summary > Podcast transcript </ summary >
< p > Full transcript text here... </ p >
Users must be able to navigate and use controls. Ensure keyboard access:
<!-- All functionality available via keyboard -->
< button onclick = " doSomething () " > Action </ button >
<!-- Focus visible on interactive elements -->
outline : 3 px solid # 0066ff ;
<!-- Skip to main content link -->
< a href = " #main " class = " skip-link " > Skip to main content </ a >
Content must be clear and predictable:
<!-- Clear page structure -->
<!-- Instructions provided -->
Password (minimum 8 characters)
< input type = " password " id = " password " minlength = " 8 " >
<!-- Error messages help users fix problems -->
< input type = " email " id = " email " required aria-describedby = " email-error " >
< span id = " email-error " role = " alert " > Invalid email format </ span >
Content must work with assistive technology:
< button > Click me </ button >
< article > Article </ article >
<!-- Proper ARIA when semantic HTML isn't enough -->
< div role = " button " tabindex = " 0 " onclick = " doSomething () " >
ARIA provides additional meaning to HTML when semantic elements aren’t sufficient. ARIA has three types of attributes: roles, properties, and states.
Roles define what an element does:
Interactive code playground requires JavaScript. Here's the code:
<!-- Landmark roles -->
<div role="banner">Site header</div>
<div role="navigation">Site navigation</div>
<div role="main">Main content</div>
<div role="search">Search form</div>
<div role="complementary">Sidebar</div>
<div role="contentinfo">Footer</div>
<!-- Custom widget roles -->
<div role="alert">Important notification</div>
<div role="tab">Tab panel</div>
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Dialog box</h2>
</div>
Tip
Use semantic HTML first (<header>, <nav>, <main>, etc.). Only use ARIA roles when semantic elements don’t exist.
Properties provide additional information:
Interactive code playground requires JavaScript. Here's the code:
<!-- aria-label: Accessible name for elements without visible text -->
<button aria-label="Close menu">X</button>
<!-- aria-labelledby: Link element to its label -->
<h2 id="section-title">About Us</h2>
<div aria-labelledby="section-title">
Content of the About Us section...
</div>
<!-- aria-describedby: Link element to its description -->
<input type="password"
aria-describedby="password-requirements">
<span id="password-requirements">
Minimum 8 characters, one number, one symbol
</span>
<!-- aria-required: Mark required fields -->
<input type="text" aria-required="true">
<!-- aria-readonly: Mark read-only fields -->
<input type="text" value="Cannot change" aria-readonly="true">
States change based on user interaction:
Interactive code playground requires JavaScript. Here's the code:
<!-- aria-expanded: Shows if content is expanded -->
<button aria-expanded="false" aria-controls="menu">
Open menu
</button>
<ul id="menu" hidden>
<li>Option 1</li>
<li>Option 2</li>
</ul>
<!-- aria-selected: Current selection in a tab list -->
<div role="tablist">
<button role="tab" aria-selected="true">Tab 1</button>
<button role="tab" aria-selected="false">Tab 2</button>
</div>
<!-- aria-hidden: Hide decorative elements from screen readers -->
<span aria-hidden="true">→</span>
<!-- aria-busy: Loading state -->
<div aria-busy="true" role="status">
Loading content...
</div>
<!-- aria-live: Dynamic content updates -->
<div aria-live="polite" aria-atomic="true">
Cart items: 3
</div>
Keyboard users need to access everything via keyboard. Focus management is critical:
Interactive code playground requires JavaScript. Here's the code:
<!-- Keyboard accessible button -->
<button type="button">Click me</button>
<!-- Custom keyboard handler -->
<div role="button"
tabindex="0"
onkeydown="if(event.key==='Enter' || event.key===' ') { doAction(); }">
Custom button (press Enter or Space)
</div>
<!-- Skip link for keyboard users -->
<a href="#main" style="position: absolute; top: -9999px;">
Skip to main content
</a>
<!-- Focus visible styling -->
<style>
button:focus {
outline: 3px solid #0066ff;
}
button:focus:not(:focus-visible) {
outline: none;
}
</style>
By default, tabindex goes in source order. For complex layouts, manage focus:
<!-- Tab order: Skip decorative content -->
< button tabindex = " 0 " > Important button </ button >
< img src = " decoration.png " tabindex = " -1 " alt = "" >
< button tabindex = " 0 " > Another button </ button >
<!-- Good: Use source order naturally -->
< label > Name: < input type = " text " ></ label >
< label > Email: < input type = " email " ></ label >
< button type = " submit " > Submit </ button >
Text needs sufficient contrast with the background. WCAG requires:
Level A : Contrast ratio of at least 3:1
Level AA : Contrast ratio of at least 4.5:1 (for normal text)
Level AAA : Contrast ratio of at least 7:1
Interactive code playground requires JavaScript. Here's the code:
<!-- Good contrast: Dark text on light background -->
<div style="background: white; color: #333;">
Good contrast: Easy to read
</div>
<!-- Poor contrast: Light gray on white (BAD) -->
<div style="background: white; color: #ddd;">
Poor contrast: Hard to read
</div>
<!-- Better: Higher contrast -->
<div style="background: #f5f5f5; color: #000;">
Better contrast: Easier to read
</div>
Tools:
WebAIM Contrast Checker — Check contrast ratios
Chrome DevTools Accessibility — Built-in contrast checker
Lighthouse — Audits contrast issues
Screen readers announce content differently. Structure your HTML properly:
Interactive code playground requires JavaScript. Here's the code:
<!-- Screen readers understand structure -->
<article>
<h1>Article Title</h1>
<p>Published: <time datetime="2024-12-10">December 10, 2024</time></p>
<p>Content...</p>
</article>
<!-- Landmarks help navigation -->
<nav role="navigation">
<h2 style="font-size: 1rem;">Navigation</h2>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<!-- Icons and decorative images -->
< img src = " bullet.png " alt = "" >
< i class = " icon-arrow " aria-hidden = " true " ></ i >
<!-- Decorative dividers -->
< span aria-hidden = " true " > • </ span >
<!-- Good: Link text is descriptive -->
< a href = " /about " > Learn about our company </ a >
<!-- Bad: Vague link text -->
< a href = " /about " > Click here </ a >
<!-- Make icons accessible -->
< a href = " /search " aria-label = " Search the site " >
< i class = " icon-search " ></ i >
Forms are critical for accessibility:
Interactive code playground requires JavaScript. Here's the code:
<!-- Always use label elements -->
<label for="name">Name:</label>
<input type="text" id="name" required>
<!-- Fieldset for grouping -->
<fieldset>
<legend>What is your experience level?</legend>
<label>
<input type="radio" name="level" value="beginner">
Beginner
</label>
<label>
<input type="radio" name="level" value="expert">
Expert
</label>
</fieldset>
<!-- Error messages with ARIA -->
<input type="email" id="email" aria-describedby="email-error">
<span id="email-error" role="alert">Invalid email</span>
<!-- Complex form section -->
<fieldset>
<legend>Billing Address</legend>
<!-- Form fields -->
</fieldset>
Interactive code playground requires JavaScript. Here's the code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Accessible Web Development</title>
</head>
<body>
<!-- Skip link for keyboard users -->
<a href="#main" style="position: absolute; top: -40px;">
Skip to main content
</a>
<header>
<h1>Accessible Web Development</h1>
<p>Building inclusive web experiences</p>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<main id="main">
<article>
<h2>Why Accessibility Matters</h2>
<p>Over 15% of the world population has disabilities...</p>
<section>
<h3>Getting Started</h3>
<ol>
<li>Use semantic HTML</li>
<li>Test with keyboard navigation</li>
<li>Check color contrast</li>
<li>Add ARIA when needed</li>
</ol>
</section>
<section>
<h3>Resources</h3>
<ul>
<li>
<a href="https://www.w3.org/WAI/">
W3C Web Accessibility Initiative
</a>
</li>
<li>
<a href="https://www.webaim.org/">
WebAIM: Web Accessibility In Mind
</a>
</li>
</ul>
</section>
</article>
<aside>
<h3>Quick Links</h3>
<ul>
<li><a href="#semantics">Semantic HTML</a></li>
<li><a href="#aria">ARIA Attributes</a></li>
<li><a href="#testing">Testing</a></li>
</ul>
</aside>
</main>
<footer>
<p>© 2024. All content accessible.</p>
</footer>
</body>
</html>
NVDA — Free screen reader (Windows)
JAWS — Popular but commercial (Windows)
VoiceOver — Built into macOS/iOS
Lighthouse — Chrome DevTools auditing
WAVE — Browser extension for WCAG issues
Axe DevTools — Comprehensive accessibility testing
WCAG 2.1 has four principles: Perceivable, Operable, Understandable, Robust
Use semantic HTML first, ARIA only when needed
Ensure keyboard navigation with proper focus management
Maintain sufficient color contrast (4.5:1 minimum for normal text)
Label all form inputs with <label> elements
Provide alt text for all images
Test with keyboard navigation and screen readers
Use skip links and landmark regions
SEO Optimization
Advanced technical SEO with HTML.
Continue →
Custom Elements
Introduction to Web Components and custom elements.
Continue →