HTML Accessibility: Building Inclusive Websites
Learn how to create websites that work for everyone, including people with disabilities, using proper HTML markup and ARIA attributes.
Introduction to Web Accessibility
Web accessibility ensures that websites, tools, and technologies are designed and developed so that people with disabilities can use them. Specifically, people can perceive, understand, navigate, interact with, and contribute to the web.
Accessibility not only benefits people with permanent disabilities but also those with temporary impairments, situational limitations, and aging users. Creating accessible websites isn't just a moral imperative—it's a legal requirement in many countries under legislation like the ADA (Americans with Disabilities Act) and the European Accessibility Act.
HTML serves as the foundation for accessible websites. When used correctly, HTML provides inherent accessibility benefits that help ensure your content is available to all users. This guide covers the essential HTML techniques and best practices for creating accessible websites that everyone can use.
Why Accessibility Matters
- Around 15% of the world's population lives with some form of disability
- Legal requirements in many jurisdictions mandate accessibility
- Accessible websites reach larger audiences
- Many accessibility features improve usability for all users
- Proper HTML semantics benefit SEO and accessibility simultaneously
Semantic HTML for Accessibility
Semantic HTML uses elements that convey meaning about their content, not just their presentation. Using the right HTML elements for their intended purpose is the foundation of web accessibility.
Non-Semantic vs. Semantic HTML
<!-- Non-Semantic Approach -->
<div class="header">
<div class="site-title">My Website</div>
<div class="navigation">
<div class="nav-item"><a href="/">Home</a></div>
<div class="nav-item"><a href="/about">About</a></div>
</div>
</div>
<div class="main-content">
<div class="article">
<div class="article-title">Article Title</div>
<div class="article-content">Content goes here...</div>
</div>
</div>
<div class="footer">Copyright 2023</div>
<!-- Semantic Approach -->
<header>
<h1>My Website</h1>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h2>Article Title</h2>
<p>Content goes here...</p>
</article>
</main>
<footer>Copyright 2023</footer>
The semantic approach provides numerous accessibility benefits:
- Screen readers announce elements based on their semantic roles
- Keyboard users can navigate between landmark elements
- Search engines better understand your content structure
- Styles and scripts can target elements by their semantic meaning
- Content is understandable even without CSS
Semantic HTML Best Practices
- Use heading elements (
<h1>
through<h6>
) in a logical hierarchical order - Create proper navigation with
<nav>
and list elements - Employ
<main>
,<article>
,<section>
, and<aside>
to structure content - Wrap related form elements with
<fieldset>
and<legend>
- Use
<button>
elements for interactive controls instead of styled<div>
s
ARIA Attributes and Their Usage
Accessible Rich Internet Applications (ARIA) is a set of attributes that define ways to make web content and applications more accessible. ARIA supplements HTML when native semantics aren't sufficient.
The first rule of ARIA is: don't use ARIA if native HTML can achieve the same result. ARIA doesn't change element behavior—it only affects how the element is announced to assistive technologies.
Common ARIA Attributes
<!-- Role Attribute -->
<div role="button" tabindex="0" onclick="activateButton()">Click Me</div>
<!-- Landmark Roles -->
<div role="navigation">...</div>
<div role="main">...</div>
<!-- States and Properties -->
<button aria-expanded="false" aria-controls="dropdown-menu">Menu</button>
<div id="dropdown-menu" hidden>...</div>
<!-- Live Regions -->
<div aria-live="polite" aria-atomic="true">
<p id="status-message"></p>
</div>
<!-- Labels and Descriptions -->
<button aria-label="Close dialog" aria-describedby="desc-close">×</button>
<div id="desc-close" hidden>Closes the current dialog and discards any unsaved changes</div>
ARIA Categories
- Roles — Define what an element is or does
- Properties — Define properties of objects that are unlikely to change
- States — Define the current state of an element (often changing)
- Live Regions — Areas that update dynamically and need announcements
Common ARIA patterns you should know:
Accessible Accordion Example
<div class="accordion">
<h3>
<button aria-expanded="false" aria-controls="section1-content" id="accordion1-header">
Section 1
</button>
</h3>
<div id="section1-content" role="region" aria-labelledby="accordion1-header" hidden>
<p>Content for section 1...</p>
</div>
<h3>
<button aria-expanded="false" aria-controls="section2-content" id="accordion2-header">
Section 2
</button>
</h3>
<div id="section2-content" role="region" aria-labelledby="accordion2-header" hidden>
<p>Content for section 2...</p>
</div>
</div>
ARIA Best Practices
- Use native HTML elements whenever possible
- Don't change the meaning of native HTML elements with ARIA
- Make all interactive ARIA controls keyboard accessible
- Keep ARIA attributes updated when state changes
- Test with actual screen readers, not just automated tools
Keyboard Navigation and Focus Management
Many people with disabilities rely on keyboards or keyboard-like devices to navigate websites. Ensuring your site is fully operable without a mouse is essential for accessibility.
Key considerations for keyboard accessibility include:
- Logical tab order that follows the visual layout
- Visible focus indicators for all interactive elements
- Ensuring all interactive elements are keyboard accessible
- Managing focus movement during interactions like modals and dropdowns
Skip Navigation Link
<!-- Skip navigation link (placed at the beginning of the page) -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<!-- Rest of the header and navigation -->
<header>...</header>
<nav>...</nav>
<!-- Main content with matching ID -->
<main id="main-content">
<h1>Page Title</h1>
<!-- Page content -->
</main>
/* CSS for skip link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
Managing Focus in Modal Dialogs
<!-- Button to open the modal -->
<button id="open-dialog">Open Dialog</button>
<!-- Modal dialog -->
<div id="dialog" role="dialog" aria-labelledby="dialog-title" aria-modal="true" hidden>
<div class="dialog-content">
<h2 id="dialog-title">Dialog Title</h2>
<p>Dialog content goes here...</p>
<button id="close-dialog">Close</button>
</div>
</div>
// JavaScript for managing focus
const openButton = document.getElementById('open-dialog');
const dialog = document.getElementById('dialog');
const closeButton = document.getElementById('close-dialog');
// Open dialog and set focus to the first focusable element
openButton.addEventListener('click', () => {
dialog.removeAttribute('hidden');
closeButton.focus(); // Focus first interactive element
// Store the element that had focus before the dialog opened
dialog.previousFocus = document.activeElement;
});
// Close dialog and restore focus
closeButton.addEventListener('click', () => {
dialog.setAttribute('hidden', 'true');
// Return focus to the element that had focus before the dialog opened
if (dialog.previousFocus) {
dialog.previousFocus.focus();
}
});
// Trap focus within the dialog
dialog.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeButton.click();
}
});
Keyboard Navigation Best Practices
- Make all interactive controls focusable and operable with the keyboard
- Use proper HTML elements (
<button>
,<a>
, etc.) for interactive elements - Provide a way to skip repetitive navigation
- Ensure a logical tab order that matches visual layout
- Maintain visible focus indicators (never use
outline: none
without an alternative) - Trap focus within modal dialogs and similar components
- Return focus to triggering element when closing temporary UIs
Screen Reader Optimization
Screen readers are assistive technologies that convert digital text into synthesized speech or braille output. They rely heavily on proper HTML semantics and additional ARIA attributes.
Optimizing Content for Screen Readers
<!-- Use proper heading hierarchy -->
<h1>Page Title</h1>
<section>
<h2>Section Title</h2>
<p>Content...</p>
<h3>Subsection Title</h3>
<p>More content...</p>
</section>
<!-- Properly label form elements -->
<label for="username">Username</label>
<input type="text" id="username" name="username">
<!-- Make non-standard controls accessible -->
<div role="checkbox"
tabindex="0"
aria-checked="false"
class="custom-checkbox">
Accept terms and conditions
</div>
<!-- Announce dynamic content changes -->
<div aria-live="polite">
<p id="status"></p>
</div>
<!-- Hide decorative elements from screen readers -->
<img src="decorative-divider.png" alt="" aria-hidden="true">
Screen Reader Best Practices
- Provide descriptive alt text for informative images
- Use empty alt text (
alt=""
) for decorative images - Explicitly label form controls with
<label>
elements - Create a logical heading hierarchy (h1-h6)
- Provide context for links (avoid "click here" or "read more" without context)
- Use
aria-live
regions for dynamic content changes - Test with multiple screen readers (NVDA, JAWS, VoiceOver)
Color Contrast and Visual Considerations
Visual accessibility ensures that content is perceivable for users with low vision, color blindness, or other visual impairments. Proper color contrast is the foundation of visual accessibility.
The Web Content Accessibility Guidelines (WCAG) establish minimum contrast ratios:
- Text and images of text should have a contrast ratio of at least 4.5:1 (WCAG AA)
- Large text (18pt or 14pt bold) should have a contrast ratio of at least 3:1 (WCAG AA)
- User interface components and graphical objects should have a contrast ratio of at least 3:1 against adjacent colors
Text Resizing and Zooming
/* Using relative units for text and spacing */
body {
font-size: 16px; /* Base font size */
}
h1 {
font-size: 2em; /* Relative to base font size (32px) */
}
p {
font-size: 1rem; /* Relative to root element (16px) */
line-height: 1.5; /* Relative to the element's font size */
}
.container {
max-width: 1200px;
width: 90%; /* Responsive width */
margin: 0 auto;
padding: 1rem; /* Spacing scales with font size */
}
Visual Accessibility Best Practices
- Use sufficient color contrast for text and UI elements
- Don't rely on color alone to convey information
- Ensure text can be resized up to 200% without loss of functionality
- Use relative units (em, rem, %) instead of absolute units (px)
- Design for different viewport sizes and orientations
- Provide visible focus indicators for keyboard navigation
- Consider offering a high-contrast mode or theme switcher
Tools for Checking Color Contrast
- WebAIM Contrast Checker
- Colour Contrast Analyzer
- Chrome DevTools' Accessibility Audit
- Axe Accessibility Testing Tool
Creating Accessible Forms
Forms are crucial interactive elements on the web, and making them accessible ensures all users can complete tasks and provide information. Accessible forms provide clear labels, helpful instructions, and proper error handling.
Accessible Form Example
<form action="/submit" method="post">
<fieldset>
<legend>Personal Information</legend>
<div class="form-group">
<label for="full-name">Full Name</label>
<input type="text" id="full-name" name="fullName" required
aria-describedby="name-format">
<div id="name-format" class="help-text">Enter your first and last name</div>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input type="email" id="email" name="email" required
aria-describedby="email-help">
<div id="email-help" class="help-text">We'll never share your email with anyone else</div>
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input type="tel" id="phone" name="phone"
aria-describedby="phone-format"
pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}">
<div id="phone-format" class="help-text">Format: 123-456-7890</div>
</div>
</fieldset>
<fieldset>
<legend>Preferences</legend>
<div class="form-group">
<label for="topic">Preferred Topic</label>
<select id="topic" name="topic">
<option value="">Select a topic...</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
<option value="js">JavaScript</option>
</select>
</div>
<div class="form-group checkbox-group">
<p class="checkbox-legend">Contact Preferences</p>
<div class="checkbox">
<input type="checkbox" id="contact-email" name="contact" value="email">
<label for="contact-email">Email</label>
</div>
<div class="checkbox">
<input type="checkbox" id="contact-phone" name="contact" value="phone">
<label for="contact-phone">Phone</label>
</div>
</div>
</fieldset>
<div class="form-actions">
<button type="submit">Submit</button>
<button type="reset">Reset</button>
</div>
</form>
/* Basic form styling */
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
.help-text {
margin-top: 0.25rem;
font-size: 0.875rem;
color: #555;
}
fieldset {
margin-bottom: 2rem;
border: 1px solid #ddd;
padding: 1rem;
border-radius: 4px;
}
legend {
font-weight: bold;
padding: 0 0.5rem;
}
input, select, textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
}
input:focus, select:focus, textarea:focus {
outline: 2px solid #4a90e2;
border-color: #4a90e2;
}
.checkbox-group {
margin-top: 1rem;
}
.checkbox {
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.checkbox input {
width: auto;
margin-right: 0.5rem;
}
.checkbox label {
margin-bottom: 0;
font-weight: normal;
}
.checkbox-legend {
font-weight: bold;
margin-bottom: 0.5rem;
}
.form-actions {
margin-top: 2rem;
}
button {
padding: 0.75rem 1.5rem;
background-color: #4a90e2;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 0.5rem;
}
button[type="reset"] {
background-color: #f1f1f1;
color: #333;
}
Form Validation and Error Messaging
<form id="contact-form" novalidate>
<div class="form-group">
<label for="user-email">Email Address</label>
<input type="email" id="user-email" name="email" required
aria-describedby="email-error email-hint">
<div id="email-hint" class="hint">Enter your email address</div>
<div id="email-error" class="error" role="alert" aria-live="assertive" hidden>
Please enter a valid email address
</div>
</div>
<button type="submit">Submit</button>
</form>
document.getElementById('contact-form').addEventListener('submit', function(event) {
event.preventDefault();
// Get the email input and error message elements
const emailInput = document.getElementById('user-email');
const emailError = document.getElementById('email-error');
// Simple validation
if (!validateEmail(emailInput.value)) {
// Show error message
emailError.hidden = false;
// Set input as invalid
emailInput.setAttribute('aria-invalid', 'true');
emailInput.focus();
} else {
// Hide error message
emailError.hidden = true;
// Set input as valid
emailInput.removeAttribute('aria-invalid');
// Form is valid, could submit here
console.log('Form is valid, submitting...');
}
});
function validateEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
Form Accessibility Best Practices
- Label all form controls with
<label>
elements - Group related form controls with
<fieldset>
and<legend>
- Provide instructions and hints with
aria-describedby
- Use HTML5 validation attributes (
required
,pattern
, etc.) - Ensure keyboard accessibility for all form controls
- Provide clear, specific error messages
- Use
aria-invalid
androle="alert"
for error states - Maintain logical tab order
- Use appropriate input types (
email
,tel
,date
, etc.)
Accessible Tables and Data
Tables are used to present tabular data in rows and columns. Making tables accessible ensures that screen reader users can understand the relationships between data cells and headers.
Basic Accessible Table
<table>
<caption>Monthly Subscription Plans</caption>
<thead>
<tr>
<th scope="col">Plan</th>
<th scope="col">Price</th>
<th scope="col">Features</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Basic</th>
<td>$9.99/month</td>
<td>10 Users, 10GB Storage, Basic Support</td>
</tr>
<tr>
<th scope="row">Pro</th>
<td>$19.99/month</td>
<td>50 Users, 100GB Storage, Priority Support</td>
</tr>
<tr>
<th scope="row">Enterprise</th>
<td>$49.99/month</td>
<td>Unlimited Users, 1TB Storage, 24/7 Support</td>
</tr>
</tbody>
</table>
Complex Table with Multiple Headers
<table>
<caption>Quarterly Sales by Department</caption>
<thead>
<tr>
<th scope="col">Department</th>
<th scope="col">Q1</th>
<th scope="col">Q2</th>
<th scope="col">Q3</th>
<th scope="col">Q4</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Electronics</th>
<td>$10,000</td>
<td>$12,500</td>
<td>$15,000</td>
<td>$20,000</td>
</tr>
<tr>
<th scope="row">Clothing</th>
<td>$8,000</td>
<td>$9,500</td>
<td>$11,000</td>
<td>$15,500</td>
</tr>
<tr>
<th scope="row">Food</th>
<td>$5,000</td>
<td>$5,500</td>
<td>$6,000</td>
<td>$6,500</td>
</tr>
</tbody>
<tfoot>
<tr>
<th scope="row">Total</th>
<td>$23,000</td>
<td>$27,500</td>
<td>$32,000</td>
<td>$42,000</td>
</tr>
</tfoot>
</table>
Table with Row and Column Groups
<table>
<caption>Course Schedule</caption>
<colgroup>
<col>
<col span="2" class="morning">
<col span="2" class="afternoon">
</colgroup>
<thead>
<tr>
<th scope="col">Day</th>
<th scope="col">9:00 AM</th>
<th scope="col">11:00 AM</th>
<th scope="col">1:00 PM</th>
<th scope="col">3:00 PM</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Monday</th>
<td>HTML Basics</td>
<td>CSS Intro</td>
<td>JavaScript</td>
<td>Workshop</td>
</tr>
<tr>
<th scope="row">Tuesday</th>
<td>HTML Forms</td>
<td>CSS Layout</td>
<td>DOM Manipulation</td>
<td>Workshop</td>
</tr>
</tbody>
</table>
Table Accessibility Best Practices
- Always include table headers with appropriate
scope
attributes - Use
<caption>
to provide an overall description of the table - Include a summary of complex tables for screen reader users
- Use table sections (
<thead>
,<tbody>
,<tfoot>
) - For complex tables, use
id
andheaders
attributes to associate data cells with headers - Avoid using tables for layout purposes
- Consider responsive techniques for small screens
Accessible Images, Audio, and Video
Multimedia content needs to be accessible to users with various disabilities, including those who are blind, deaf, or have cognitive impairments. Proper text alternatives and captions are essential for accessibility.
Accessible Images
<!-- Informative image with alt text -->
<img src="diagram.png" alt="Flowchart showing the user authentication process">
<!-- Decorative image with empty alt text -->
<img src="decorative-divider.png" alt="">
<!-- Image with longer description -->
<figure>
<img src="chart.png" alt="Bar chart showing sales growth"
aria-describedby="chart-desc">
<figcaption id="chart-desc">
Sales growth by quarter for 2022, showing a 15% increase in Q1,
22% in Q2, 18% in Q3, and 30% in Q4.
</figcaption>
</figure>
Accessible Video
<!-- Video with captions, transcript, and audio description -->
<figure>
<video controls preload="metadata">
<source src="tutorial.mp4" type="video/mp4">
<source src="tutorial.webm" type="video/webm">
<track kind="captions" src="captions.vtt" srclang="en" label="English">
<track kind="descriptions" src="descriptions.vtt" srclang="en" label="Audio Descriptions">
Your browser does not support the video element.
</video>
<figcaption>
Tutorial: Creating Your First Web Page
<a href="transcript.html">View Transcript</a>
</figcaption>
</figure>
Accessible Audio
<!-- Audio with transcript -->
<figure>
<audio controls>
<source src="podcast.mp3" type="audio/mpeg">
<source src="podcast.ogg" type="audio/ogg">
Your browser does not support the audio element.
</audio>
<figcaption>
Podcast Episode 42: Web Accessibility Tips
<details>
<summary>View Transcript</summary>
<div class="transcript">
<p><strong>Host:</strong> Welcome to our podcast on web accessibility...</p>
<p><strong>Guest:</strong> Thanks for having me. I'm excited to share...</p>
<!-- Full transcript content -->
</div>
</details>
</figcaption>
</figure>
Multimedia Accessibility Best Practices
- Provide descriptive alt text for informative images
- Use empty alt text for decorative images
- Include captions for video content
- Provide audio descriptions for videos where visual information is important
- Offer transcripts for audio and video content
- Ensure media player controls are keyboard accessible
- Don't rely on auto-playing media
- Avoid content that flashes more than 3 times per second
Testing for Accessibility
Accessibility testing is a critical part of web development. It involves a combination of automated tools, manual testing, and user testing to ensure your website works for everyone.
Common Accessibility Testing Tools
- Automated checkers: WAVE, Axe, Lighthouse
- Screen readers: NVDA, JAWS, VoiceOver
- Keyboard testing: Tab through the interface
- Color contrast checkers: WebAIM Contrast Checker
- Accessibility browser extensions: Axe DevTools, WAVE
A comprehensive accessibility testing approach includes:
-
Automated Testing
Use tools like Axe, WAVE, or Lighthouse to catch common issues, but remember that automated tools can only catch about 30% of accessibility issues.
-
Manual Testing
Perform keyboard navigation testing, check screen reader announcements, review heading structure, and verify form accessibility.
-
User Testing
Whenever possible, include users with disabilities in your testing process. They can provide invaluable feedback on real-world usage.
Accessibility Testing Checklist
- Keyboard accessibility: Can you navigate and operate all functionality?
- Screen reader compatibility: Is all content properly announced?
- Color contrast: Do all text elements meet WCAG contrast requirements?
- Text resizing: Does the page work when zoomed to 200%?
- Alternative text: Do all images have appropriate alt text?
- Heading structure: Is there a logical heading hierarchy?
- Form accessibility: Are all controls properly labeled and operable?
- Multimedia: Are captions and transcripts available?
- Focus indicators: Are focus states clearly visible?
- ARIA implementation: Are ARIA attributes used correctly?
Conclusion and Additional Resources
Web accessibility is a journey, not a destination. By incorporating accessibility considerations into your HTML coding practices from the beginning, you create websites that work better for everyone. Remember that many accessibility features also improve usability for all users, such as clear navigation, proper heading structure, and descriptive link text.