HTML Accessibility Guide
Creating accessible websites is essential to ensure all users, including those with disabilities, can access and use your content. This guide covers HTML techniques and best practices for building accessible web pages.
1. Why Accessibility Matters
Web accessibility ensures that people with disabilities can perceive, understand, navigate, and interact with websites. It also benefits:
- People using mobile devices
- Older people with changing abilities
- People with temporary disabilities (e.g., broken arm)
- People with situational limitations (e.g., bright sunlight)
- People using slow internet connections
Legal Considerations
Web accessibility is also a legal requirement in many countries:
- Americans with Disabilities Act (ADA) in the USA
- Accessibility for Ontarians with Disabilities Act (AODA) in Canada
- European Accessibility Act in the EU
- Web Content Accessibility Guidelines (WCAG) - international standard
WCAG Compliance Levels:
- Level A: The most basic web accessibility features
- Level AA: Deals with the biggest barriers for users with disabilities (commonly required standard)
- Level AAA: The highest level of web accessibility
2. Semantic HTML
Using semantic HTML is the foundation of accessibility. Semantic elements convey meaning about their content to browsers and assistive technologies.
<!-- Non-semantic approach -->
<div class="header">
<div class="logo">Site Name</div>
<div class="nav">
<div class="nav-item">Home</div>
<div class="nav-item">About</div>
<div class="nav-item">Contact</div>
</div>
</div>
<div class="main">
<div class="title">Page Title</div>
<div class="content">Content goes here...</div>
</div>
<div class="footer">Copyright 2025</div>
<!-- Semantic approach -->
<header>
<h1>Site Name</h1>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<main>
<h2>Page Title</h2>
<p>Content goes here...</p>
</main>
<footer>Copyright 2025</footer>
Key Semantic Elements
Element |
Purpose |
Accessibility Benefit |
<header> |
Introductory content, navigation |
Creates a "banner" landmark |
<nav> |
Navigation links |
Creates a "navigation" landmark |
<main> |
Main content of the page |
Creates a "main" landmark |
<article> |
Self-contained content |
Identifies standalone content |
<section> |
Thematic grouping of content |
Creates logical content sections |
<aside> |
Related but separate content |
Creates a "complementary" landmark |
<footer> |
Footer information |
Creates a "contentinfo" landmark |
<button> |
Interactive button |
Built-in keyboard accessibility and role |
Landmark Navigation: Screen reader users can navigate between landmarks, making it easier to find and understand the structure of the page.
3. Text Accessibility
Headings Structure
Proper heading structure creates a hierarchical outline of the page content:
<!-- Good heading structure -->
<h1>Main Page Title</h1>
<section>
<h2>Section Title</h2>
<p>Section content...</p>
<article>
<h3>Article Title</h3>
<p>Article content...</p>
<h4>Subsection Title</h4>
<p>Subsection content...</p>
</article>
</section>
<!-- Bad heading structure -->
<h1>Main Page Title</h1>
<h3>This skips h2</h3> <!-- Avoid skipping heading levels -->
<h6>Using h6 for styling</h6> <!-- Don't use headings for styling purposes -->
Text Alternatives
<!-- Images with alt text -->
<img src="logo.png" alt="Company Logo">
<!-- Decorative images should have empty alt text -->
<img src="decorative-line.png" alt="">
<!-- Complex images need more detailed descriptions -->
<figure>
<img src="chart.png" alt="Bar chart showing sales growth from 2020 to 2025">
<figcaption>Figure 1: Annual sales growth by quarter, showing 15% increase year-over-year.</figcaption>
</figure>
<!-- SVG with accessible text -->
<svg width="100" height="100" role="img" aria-labelledby="svg-title">
<title id="svg-title">Circle icon with checkmark</title>
<!-- SVG content here -->
</svg>
Text Formatting for Accessibility
- Use adequate text size (minimum 16px body text)
- Maintain proper line height (1.5 times font size)
- Ensure sufficient letter spacing
- Use proper text alignment (left-aligned for languages like English)
- Provide sufficient color contrast (4.5:1 ratio for normal text, 3:1 for large text)
- Don't rely solely on color to convey information
4. Forms Accessibility
Accessible Form Structure
<!-- Using proper labels -->
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<!-- Fieldsets for grouping related fields -->
<fieldset>
<legend>Contact Information</legend>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="phone">Phone:</label>
<input type="tel" id="phone" name="phone">
</div>
</fieldset>
<!-- Providing clear instructions -->
<label for="password">Password:</label>
<p id="password-hint" class="hint">Password must be at least 8 characters with at least one number and one special character.</p>
<input
type="password"
id="password"
name="password"
aria-describedby="password-hint"
pattern="^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{8,}$"
required
>
Error Messages
<!-- Accessible error messages -->
<div class="form-group">
<label for="email2">Email:</label>
<input
type="email"
id="email2"
name="email"
aria-describedby="email-error"
aria-invalid="true"
>
<div id="email-error" class="error" role="alert">
Please enter a valid email address
</div>
</div>
Form Controls
<!-- Accessible checkboxes and radio buttons -->
<fieldset>
<legend>Subscription Options</legend>
<div class="form-check">
<input type="radio" id="basic" name="plan" value="basic">
<label for="basic">Basic Plan</label>
</div>
<div class="form-check">
<input type="radio" id="premium" name="plan" value="premium">
<label for="premium">Premium Plan</label>
</div>
</fieldset>
<div class="form-check">
<input type="checkbox" id="newsletter" name="newsletter">
<label for="newsletter">Subscribe to newsletter</label>
</div>
<!-- Accessible select element -->
<div class="form-group">
<label for="country">Country:</label>
<select id="country" name="country">
<option value="">Select a country</option>
<option value="us">United States</option>
<option value="ca">Canada</option>
<option value="uk">United Kingdom</option>
</select>
</div>
5. ARIA Attributes
ARIA (Accessible Rich Internet Applications) attributes provide additional information to assistive technologies to enhance accessibility, especially for dynamic and complex components.
Important: The first rule of ARIA is "Don't use ARIA if you can use native HTML elements." Only use ARIA when native HTML elements can't provide the necessary semantics.
ARIA Roles
<!-- Adding roles -->
<div role="alert">Your session will expire in 5 minutes.</div>
<div role="tablist">
<button role="tab" aria-selected="true" aria-controls="panel1">Tab 1</button>
<button role="tab" aria-selected="false" aria-controls="panel2">Tab 2</button>
</div>
<div id="panel1" role="tabpanel">Content for tab 1</div>
<div id="panel2" role="tabpanel" hidden>Content for tab 2</div>
ARIA States and Properties
Attribute |
Description |
Example |
aria-label |
Provides a text label for an element |
<button aria-label="Close">×</button> |
aria-labelledby |
Points to the ID of an element that labels the current element |
<div id="title">User Profile</div> <section aria-labelledby="title">...</section> |
aria-describedby |
Points to the ID of an element that describes the current element |
<input aria-describedby="hint"> <p id="hint">Enter your 16-digit card number</p> |
aria-expanded |
Indicates if a control is expanded or collapsed |
<button aria-expanded="false">Show more</button> |
aria-hidden |
Hides elements from assistive technologies |
<div aria-hidden="true">Decorative content</div> |
aria-live |
Indicates an area will update and how to announce changes |
<div aria-live="polite">Updated content here</div> |
aria-required |
Indicates that input is required |
<input aria-required="true"> |
ARIA Live Regions
<!-- Polite update (doesn't interrupt user) -->
<div aria-live="polite" role="status">
You have 3 new messages
</div>
<!-- Assertive update (interrupts current task) -->
<div aria-live="assertive" role="alert">
Your session will expire in 30 seconds
</div>
<!-- For complex live regions -->
<div aria-live="polite" aria-atomic="true" aria-relevant="additions text">
<p>Status: <span id="status-text">Processing...</span></p>
</div>
6. Keyboard Accessibility
Many users navigate websites using only a keyboard, including people with motor disabilities, visual impairments, or those using alternative input devices.
Focus Management
<!-- Ensure all interactive elements can receive focus -->
<a href="#">This link is focusable by default</a>
<button type="button">This button is focusable by default</button>
<div tabindex="0" role="button" aria-pressed="false">
Custom button (made focusable with tabindex="0")
</div>
<!-- Remove non-interactive elements from tab order -->
<p tabindex="-1">This paragraph can be programmatically focused but not in tab order</p>
<!-- Never use tabindex greater than 0 -->
<div tabindex="1">This breaks natural tab order (avoid this)</div>
Skip Links
<!-- Skip link at the top of the page -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- CSS to position the skip link -->
<style>
.skip-link {
position: absolute;
top: -40px;
left: 0;
padding: 8px;
background-color: white;
z-index: 100;
transition: top 0.3s;
}
.skip-link:focus {
top: 0;
}
</style>
<!-- Target ID in content -->
<main id="main-content">
<!-- Main content here -->
</main>
Keyboard Event Handling
<!-- Managing keyboard events for custom components -->
<div
role="button"
tabindex="0"
aria-pressed="false"
onclick="toggleButton(this)"
onkeydown="handleKeydown(event, this)"
>
Toggle Button
</div>
<script>
function toggleButton(button) {
const isPressed = button.getAttribute('aria-pressed') === 'true';
button.setAttribute('aria-pressed', !isPressed);
}
function handleKeydown(event, button) {
// Handle Space or Enter key press
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
toggleButton(button);
}
}
</script>
7. Media Accessibility
Accessible Audio
<!-- Accessible audio player -->
<figure>
<figcaption>Interview with Jane Doe</figcaption>
<audio controls>
<source src="interview.mp3" type="audio/mpeg">
<source src="interview.ogg" type="audio/ogg">
<p>Your browser doesn't support HTML5 audio. Here is a <a href="interview.mp3">link to download the audio</a> instead.</p>
</audio>
<a href="interview-transcript.html">Read the transcript</a>
</figure>
Accessible Video
<!-- Accessible video with captions and transcripts -->
<figure>
<figcaption>Product Demonstration Video</figcaption>
<video controls width="640" height="360">
<source src="demo.mp4" type="video/mp4">
<source src="demo.webm" type="video/webm">
<track kind="captions" src="demo-captions.vtt" srclang="en" label="English" default>
<track kind="descriptions" src="demo-descriptions.vtt" srclang="en" label="Audio Descriptions">
<p>Your browser doesn't support HTML5 video. Here is a <a href="demo.mp4">link to download the video</a> instead.</p>
</video>
<a href="demo-transcript.html">Read the full transcript</a>
</figure>
Caption and Transcript Formats
<!-- Example WebVTT caption file (demo-captions.vtt) -->
WEBVTT
00:00:01.000 --> 00:00:04.000
Welcome to our product demonstration video.
00:00:05.000 --> 00:00:09.000
Today we'll be showing you how to use our new software.
00:00:10.000 --> 00:00:15.000
First, click on the "New Project" button in the top-left corner.
8. Color and Contrast
Contrast Requirements
- Normal Text (under 18pt or 14pt bold): Minimum ratio of 4.5:1
- Large Text (at least 18pt or 14pt bold): Minimum ratio of 3:1
- UI Components and Graphical Objects: Minimum ratio of 3:1
Don't Rely on Color Alone
<!-- Bad example (color only) -->
<p>
Fields marked in <span style="color: red;">red</span> are required.
</p>
<!-- Better example (color + symbol) -->
<p>
Fields marked with <span style="color: red;">* (asterisk)</span> are required.
</p>
<!-- Bad example (status with color only) -->
<div class="status-indicator" style="background-color: green;"></div>
<!-- Better example (color + text) -->
<div class="status-indicator" style="background-color: green;">
<span>Active</span>
</div>
Tools for Testing Contrast:
- WebAIM Contrast Checker
- Colour Contrast Analyser
- Chrome DevTools' Accessibility Audit
- Firefox Accessibility Inspector
9. Accessibility Testing
Manual Testing
- Keyboard Navigation: Tab through the entire page to ensure all interactive elements are reachable and operable
- Screen Reader Testing: Use screen readers (VoiceOver, NVDA, JAWS) to navigate your site
- Zoom Testing: Test with page zoom at 200% and 400%
- Contrast Testing: Check all text and interactive elements for sufficient contrast
- Disable Styles: Review the page with CSS disabled to check content structure
Automated Testing Tools
- Lighthouse (built into Chrome DevTools)
- axe DevTools
- WAVE (Web Accessibility Evaluation Tool)
- Pa11y
- Accessibility Insights
Remember: Automated tools can only catch about 30-40% of accessibility issues. Manual testing is essential for a thorough assessment.
10. Best Practices
- Start with semantic HTML to create a strong foundation
- Ensure keyboard navigability for all interactive elements
- Provide text alternatives for all non-text content
- Maintain a clear heading structure (h1-h6)
- Use adequate color contrast for all text and interactive elements
- Don't rely solely on color to convey information
- Label all form controls and provide clear instructions
- Provide captions and transcripts for media content
- Use ARIA attributes when necessary, but prefer native HTML elements
- Test with assistive technologies throughout development
- Provide skip links for keyboard users
- Ensure proper focus management for interactive components
- Use responsive design for better usability across devices
- Keep animations minimal and provide options to reduce motion