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:

Legal Considerations

Web accessibility is also a legal requirement in many countries:

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

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

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

  1. Keyboard Navigation: Tab through the entire page to ensure all interactive elements are reachable and operable
  2. Screen Reader Testing: Use screen readers (VoiceOver, NVDA, JAWS) to navigate your site
  3. Zoom Testing: Test with page zoom at 200% and 400%
  4. Contrast Testing: Check all text and interactive elements for sufficient contrast
  5. Disable Styles: Review the page with CSS disabled to check content structure

Automated Testing Tools

Remember: Automated tools can only catch about 30-40% of accessibility issues. Manual testing is essential for a thorough assessment.

10. Best Practices