Labs ICT

HTML Accessibility

Web accessibility ensures that websites are usable by everyone, including people with disabilities. By implementing proper accessibility practices in your HTML, you create inclusive experiences that work across different devices, assistive technologies, and user needs. Accessibility isn't just about compliance - it's about creating better user experiences for all visitors, improving SEO, and reaching a wider audience.

WCAG Principles

The Web Content Accessibility Guidelines (WCAG) are organized around four main principles:

  1. Perceivable: Information must be presentable in ways users can perceive
  2. Operable: Interface components must be operable
  3. Understandable: Information and UI operation must be understandable
  4. Robust: Content must be robust enough for various assistive technologies

Use Proper HTML Elements

Semantic HTML provides meaning and structure to your content, making it understandable by assistive technologies:

<!-- Good semantic structure -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>Article Title</h1>
    <section>
      <h2>Section Title</h2>
      <p>Content goes here...</p>
    </section>
  </article>
  
  <aside aria-label="Related content">
    <h2>Sidebar</h2>
    <p>Additional information</p>
  </aside>
</main>

<footer>
  <p>Copyright information</p>
</footer>

Heading Hierarchy

Maintain proper heading structure to help users navigate your content:

<!-- Correct heading hierarchy -->
<h1>Main Page Title</h1>
<h2>Primary Section</h2>
<h3>Subsection</h3>
<h3>Another Subsection</h3>
<h2>Another Primary Section</h2>
<h3>Different Subsection</h3>

<!-- Avoid this -->
<h1>Title</h1>
<h3>Skipping h2 (bad practice)</h3>
<h4>Another bad skip</h4>

Alt Text Best Practices

Alternative text (alt) describes images for screen reader users and when images fail to load:

<!-- Informative images -->
<img src="team-meeting.jpg" alt="Team of five people collaborating around a conference table with laptops and documents">

<!-- Decorative images -->
<img src="decorative-pattern.jpg" alt="">

<!-- Functional images -->
<img src="search-icon.png" alt="Search">

<!-- Complex images -->
<img src="sales-chart.jpg" alt="Bar chart showing 45% increase in quarterly sales from Q1 to Q4 2024">

<!-- Images with text -->
<img src="sale-banner.jpg" alt="Flash Sale: 50% off all items this weekend only">

When to Use Empty Alt Text

Use empty alt text (alt="") for decorative images that don't add meaningful content to the page.

Complex Images

For complex images like charts and diagrams, provide detailed descriptions:

<!-- Complex image with long description -->
<img src="complex-diagram.jpg" 
     alt="System architecture diagram showing user flow" 
     longdesc="#diagram-description">

<!-- Or use adjacent description -->
<img src="sales-chart.jpg" alt="Sales growth chart">
<p id="chart-description">
  The chart shows quarterly sales growth from Q1 ($100K) to Q4 ($180K), 
  representing an 80% increase over the year.
</p>

What is ARIA?

Accessible Rich Internet Applications (ARIA) attributes enhance accessibility when HTML semantics aren't sufficient:

Common ARIA Attributes

ARIA attributes help screen readers understand the purpose and state of interactive elements.

<!-- ARIA labels -->
<button aria-label="Close dialog">ร—</button>
<input type="search" aria-label="Search products">

<!-- ARIA descriptions -->
<input type="email" 
       aria-describedby="email-help">
<div id="email-help">Enter your work email address</div>

<!-- ARIA roles -->
<div role="navigation" aria-label="Main menu">
  <ul>
    <li><a href="/">Home</a></li>
  </ul>
</div>

<!-- ARIA states and properties -->
<button aria-expanded="false" aria-controls="menu">Menu</button>
<ul id="menu" hidden>
  <li><a href="#">Item 1</a></li>
</ul>

Landmark Roles

Landmark roles help screen readers navigate the page structure.

<!-- Using landmark roles for navigation -->
<header role="banner">
  <nav role="navigation" aria-label="Main">
    <ul>...</ul>
  </nav>
</header>

<main role="main">
  <article role="article">
    <h1>Main content</h1>
  </article>
</main>

<aside role="complementary" aria-label="Sidebar">
  <p>Additional content</p>
</aside>

<footer role="contentinfo">
  <p>Footer information</p>
</footer>

Forms and Accessibility

Proper Form Labels

Every form control must have a proper label for screen reader users:

<!-- Using label elements -->
<label for="username">Username:</label>
<input type="text" id="username" name="username">

<!-- Label wrapping input -->
<label>
  Email address:
  <input type="email" name="email">
</label>

<!-- ARIA labels when visible labels aren't possible -->
<input type="search" 
       aria-label="Search products" 
       placeholder="Search...">

Form Validation

Provide clear error messages and validation feedback for form inputs.

<!-- Accessible form validation -->
<form>
  <div>
    <label for="email">Email address:</label>
    <input type="email" 
           id="email" 
           name="email" 
           required 
           aria-describedby="email-error">
    <div id="email-error" role="alert" hidden>
      Please enter a valid email address
    </div>
  </div>
  
  <div>
    <label for="password">Password:</label>
    <input type="password" 
           id="password" 
           name="password" 
           required 
           aria-describedby="password-help password-error">
    <div id="password-help">
      Password must be at least 8 characters long
    </div>
    <div id="password-error" role="alert" hidden>
      Password is too short
    </div>
  </div>
  
  <button type="submit">Register</button>
</form>

Fieldsets and Legends

Use fieldsets to group related form controls and legends to provide a caption for the group.

<!-- Grouping related form controls -->
<fieldset>
  <legend>Shipping Information</legend>
  
  <label for="address">Street Address:</label>
  <input type="text" id="address" name="address">
  
  <label for="city">City:</label>
  <input type="text" id="city" name="city">
  
  <label for="zipcode">ZIP Code:</label>
  <input type="text" id="zipcode" name="zipcode">
</fieldset>

Keyboard Navigation

Focus Management

Ensure all interactive elements are keyboard accessible.

<!-- Skip to main content link -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<!-- Main content area -->
<main id="main-content">
  <h1>Main Content</h1>
</main>

<!-- CSS for skip link -->
<style>
.skip-link {
  position: absolute;
  top: -40px;
  left: 6px;
  background: #000;
  color: #fff;
  padding: 8px;
  text-decoration: none;
  z-index: 1000;
}

.skip-link:focus {
  top: 6px;
}
</style>

Tab Order

<!-- Logical tab order -->
<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
  <a href="/contact">Contact</a>
</nav>

<main>
  <h1>Main Content</h1>
  <button>Primary Action</button>
  <button>Secondary Action</button>
</main>

<!-- Custom tab order when needed -->
<button tabindex="1">First</button>
<button tabindex="2">Second</button>
<button tabindex="3">Third</button>

Focus Indicators

/* Clear focus indicators */
button:focus,
input:focus,
textarea:focus,
select:focus,
a:focus {
  outline: 3px solid #0066cc;
  outline-offset: 2px;
}

/* Custom focus styles */
.custom-button:focus {
  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.5);
  border-color: #0066cc;
}

Tables and Accessibility

Accessible Table Structure

Use proper table markup with captions, headers, and scope attributes.

<!-- Proper table markup -->
<table>
  <caption>Monthly Sales Report 2024</caption>
  
  <thead>
    <tr>
      <th scope="col">Month</th>
      <th scope="col">Sales</th>
      <th scope="col">Target</th>
    </tr>
  </thead>
  
  <tbody>
    <tr>
      <th scope="row">January</th>
      <td>$45,000</td>
      <td>$50,000</td>
    </tr>
    <tr>
      <th scope="row">February</th>
      <td>$52,000</td>
      <td>$50,000</td>
    </tr>
  </tbody>
  
  <tfoot>
    <tr>
      <th scope="row">Total</th>
      <td>$97,000</td>
      <td>$100,000</td>
    </tr>
  </tfoot>
</table>

Complex Tables

Use rowspan and colspan for complex table structures.

<!-- Complex table with headers and scope -->
<table>
  <caption>Product Sales by Region and Quarter</caption>
  
  <thead>
    <tr>
      <th rowspan="2" scope="col">Region</th>
      <th colspan="2" scope="colgroup">Q1 2024</th>
      <th colspan="2" scope="colgroup">Q2 2024</th>
    </tr>
    <tr>
      <th scope="col">Sales</th>
      <th scope="col">Units</th>
      <th scope="col">Sales</th>
      <th scope="col">Units</th>
    </tr>
  </thead>
  
  <tbody>
    <tr>
      <th scope="row">North</th>
      <td>$120,000</td>
      <td>450</td>
      <td>$135,000</td>
      <td>520</td>
    </tr>
  </tbody>
</table>

Color and Contrast

Color Contrast Requirements

  • Normal Text: 4.5:1 contrast ratio minimum
  • Large Text: 3:1 contrast ratio minimum
  • Non-text Elements: 3:1 contrast ratio minimum
  • AA Level: Meets minimum requirements
  • AAA Level: Enhanced contrast (7:1 for normal text)

Don't Rely on Color Alone

Use text, icons, and other visual indicators in addition to color.

<!-- Bad: Using only color to indicate status -->
<span style="color: red">Required field</span>
<span style="color: green">Complete</span>

<!-- Good: Using text and other indicators -->
<span style="color: red">* Required field</span>
<span style="color: green">* Complete</span>

<!-- Better: Using icons and text -->
<span>* Required field</span>
<span>* Complete</span>

<!-- Form validation with multiple indicators -->
<input type="text" 
       aria-invalid="true" 
       aria-describedby="error-message">
<div id="error-message" role="alert">
  * This field is required
</div>

Links and Buttons

Ensure links are distinguishable from regular text.

<!-- Ensure links are distinguishable -->
<a href="#" style="color: #0066cc; text-decoration: underline;">
  Learn more
</a>

<!-- Or use other visual indicators -->
<a href="#" class="link-button">
  Learn more
</a>

<style>
.link-button {
  color: #0066cc;
  font-weight: bold;
  border-bottom: 2px solid #0066cc;
  padding-bottom: 2px;
}

.link-button:hover {
  background-color: #f0f8ff;
}
</style>

Multimedia Accessibility

Video and Audio

Provide captions, transcripts, and audio descriptions for multimedia content.

<!-- Accessible video -->
<video controls>
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
  
  <track kind="captions" 
         src="captions.vtt" 
         srclang="en" 
         label="English captions">
  
  <track kind="descriptions" 
         src="descriptions.vtt" 
         srclang="en" 
         label="Audio descriptions">
  
  Your browser does not support the video element.
</video>

<!-- Accessible audio -->
<audio controls>
  <source src="audio.mp3" type="audio/mpeg">
  <source src="audio.ogg" type="audio/ogg">
  
  Download the <a href="audio.mp3">audio file</a>
</audio>

Transcripts

Provide transcripts for audio and video content.

<!-- Provide transcripts for audio/video -->
<div>
  <h3>Video Transcript</h3>
  <p>
    [00:00] Speaker: Welcome to our accessibility tutorial.
    [00:05] Speaker: Today we'll learn about making websites accessible.
    [00:12] Speaker: Let's start with the basics of semantic HTML...
  </p>
</div>

Testing for Accessibility

Automated Testing Tools

Use automated tools to identify accessibility issues.

  • Browser Extensions: axe DevTools, WAVE, Lighthouse
  • Online Tools: WebAIM WAVE, AChecker
  • Command Line: axe-core, pa11y
  • CI/CD Integration: Automated accessibility testing

Manual Testing

  1. Keyboard navigation - Tab through all interactive elements
  2. Screen reader testing - Use NVDA, JAWS, or VoiceOver
  3. Color contrast - Use contrast checker tools
  4. Zoom testing - Test 200% and 400% zoom levels
  5. Mobile accessibility - Test with mobile screen readers

Complete Accessible Page Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Accessible Website Example</title>
  <meta name="description" content="Example of an accessible HTML page following WCAG guidelines">
  
  <style>
    /* High contrast colors */
    body {
      font-family: Arial, sans-serif;
      line-height: 1.6;
      color: #333;
      background-color: #fff;
      max-width: 1200px;
      margin: 0 auto;
      padding: 20px;
    }
    
    /* Clear focus indicators */
    a:focus, button:focus, input:focus, textarea:focus {
      outline: 3px solid #0066cc;
      outline-offset: 2px;
    }
    
    /* Skip link */
    .skip-link {
      position: absolute;
      top: -40px;
      left: 6px;
      background: #000;
      color: #fff;
      padding: 8px;
      text-decoration: none;
      z-index: 1000;
    }
    
    .skip-link:focus {
      top: 6px;
    }
    
    /* High contrast links */
    a {
      color: #0066cc;
      text-decoration: underline;
    }
    
    a:hover {
      color: #004499;
    }
    
    /* Accessible form styling */
    label {
      display: block;
      margin-bottom: 5px;
      font-weight: bold;
    }
    
    input, textarea, select {
      border: 2px solid #ccc;
      padding: 8px;
      font-size: 16px;
      border-radius: 4px;
    }
    
    input:focus, textarea:focus, select:focus {
      border-color: #0066cc;
    }
    
    /* Error styling */
    .error {
      color: #d32f2f;
      font-weight: bold;
    }
    
    /* Success styling */
    .success {
      color: #388e3c;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <!-- Skip to main content link -->
  <a href="#main-content" class="skip-link">Skip to main content</a>
  
  <header role="banner">
    <nav role="navigation" aria-label="Main navigation">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/about">About</a></li>
        <li><a href="/services">Services</a></li>
        <li><a href="/contact">Contact</a></li>
      </ul>
    </nav>
  </header>
  
  <main id="main-content" role="main">
    <article>
      <h1>Welcome to Our Accessible Website</h1>
      
      <img src="hero-image.jpg" 
           alt="Team collaborating in modern office with accessibility features" 
           width="800" 
           height="400">
      
      <section>
        <h2>Our Commitment to Accessibility</h2>
        <p>
          We believe in creating web experiences that are accessible to everyone, 
          regardless of ability. Our website follows WCAG 2.1 AA guidelines to ensure 
          equal access to information and services.
        </p>
        
        <h3>Key Features</h3>
        <ul>
          <li>Full keyboard navigation support</li>
          <li>Screen reader compatibility</li>
          <li>High contrast color scheme</li>
          <li>Clear focus indicators</li>
          <li>Descriptive alternative text for images</li>
        </ul>
      </section>
      
      <section>
        <h2>Contact Us</h2>
        <p>
          Have questions about accessibility or need assistance? 
          We're here to help.
        </p>
        
        <form action="/contact" method="post" novalidate>
          <fieldset>
            <legend>Contact Information</legend>
            
            <div>
              <label for="name">Name *</label>
              <input type="text" 
                     id="name" 
                     name="name" 
                     required 
                     aria-describedby="name-error">
              <div id="name-error" class="error" role="alert" hidden>
                Please enter your name
              </div>
            </div>
            
            <div>
              <label for="email">Email address *</label>
              <input type="email" 
                     id="email" 
                     name="email" 
                     required 
                     aria-describedby="email-error email-help">
              <div id="email-help">
                We'll never share your email with anyone else.
              </div>
              <div id="email-error" class="error" role="alert" hidden>
                Please enter a valid email address
              </div>
            </div>
            
            <div>
              <label for="message">Message *</label>
              <textarea id="message" 
                        name="message" 
                        rows="5" 
                        required 
                        aria-describedby="message-error"></textarea>
              <div id="message-error" class="error" role="alert" hidden>
                Please enter your message
              </div>
            </div>
            
            <button type="submit">Send Message</button>
          </fieldset>
        </form>
      </section>
    </article>
  </main>
  
  <aside role="complementary" aria-label="Related information">
    <h2>Resources</h2>
    <ul>
      <li><a href="/accessibility-guide">Accessibility Guide</a></li>
      <li><a href="/wcag-guidelines">WCAG Guidelines</a></li>
      <li><a href="/testing-tools">Testing Tools</a></li>
    </ul>
  </aside>
  
  <footer role="contentinfo">
    <p>
      ยฉ 2024 Accessible Website. All rights reserved. | 
      <a href="/privacy">Privacy Policy</a> | 
      <a href="/accessibility">Accessibility Statement</a>
    </p>
  </footer>
  
  <script>
    // Form validation enhancement
    document.querySelector('form').addEventListener('submit', function(e) {
      let isValid = true;
      
      // Name validation
      const nameInput = document.getElementById('name');
      const nameError = document.getElementById('name-error');
      if (!nameInput.value.trim()) {
        nameError.hidden = false;
        nameInput.setAttribute('aria-invalid', 'true');
        isValid = false;
      } else {
        nameError.hidden = true;
        nameInput.removeAttribute('aria-invalid');
      }
      
      // Email validation
      const emailInput = document.getElementById('email');
      const emailError = document.getElementById('email-error');
      const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      if (!emailPattern.test(emailInput.value)) {
        emailError.hidden = false;
        emailInput.setAttribute('aria-invalid', 'true');
        isValid = false;
      } else {
        emailError.hidden = true;
        emailInput.removeAttribute('aria-invalid');
      }
      
      // Message validation
      const messageInput = document.getElementById('message');
      const messageError = document.getElementById('message-error');
      if (!messageInput.value.trim()) {
        messageError.hidden = false;
        messageInput.setAttribute('aria-invalid', 'true');
        isValid = false;
      } else {
        messageError.hidden = true;
        messageInput.removeAttribute('aria-invalid');
      }
      
      if (!isValid) {
        e.preventDefault();
        // Focus first error field
        const firstError = document.querySelector('[aria-invalid="true"]');
        if (firstError) {
          firstError.focus();
        }
      }
    });
  </script>
</body>
</html>

๐Ÿงช Quick Quiz

Which attribute provides alternative text for images?