11. Web Accessibility (A11y)

11. Web Accessibility (A11y)

Learning Objectives

  • Understand the importance of web accessibility and legal requirements
  • Learn WCAG guidelines and compliance levels
  • Improve accessibility using ARIA attributes
  • Implement keyboard navigation
  • Test screen reader compatibility

Table of Contents

  1. Accessibility Overview
  2. WCAG Guidelines
  3. Semantic HTML
  4. ARIA Attributes
  5. Keyboard Accessibility
  6. Testing and Tools
  7. Practice Problems

1. Accessibility Overview

1.1 What is Web Accessibility?

┌─────────────────────────────────────────────────────────────────┐
                    Web Accessibility Definition                  
                                                                 
   "Ensuring that all people, regardless of disability, can      │
    perceive, understand, navigate, and interact with web        
    content and functionality"                                   │
                                                                 
   Target Users:                                                 
   - Visual disabilities (blindness, low vision, color blindness)
   - Hearing disabilities (deafness, hard of hearing)            
   - Motor disabilities (cannot use mouse)                       
   - Cognitive disabilities (learning, attention disorders)      
   - Temporary disabilities (injury, bright environment)         
   - Situational constraints (small screen, slow connection)     
                                                                 
   "a11y" = accessibility (a + 11 letters + y)                  
└─────────────────────────────────────────────────────────────────┘

1.2 Importance of Accessibility

Legal Requirements:
- Korea: Anti-Discrimination Law, Web Accessibility Certification (KWCAG)
- USA: ADA (Americans with Disabilities Act), Section 508
- Europe: EN 301 549, European Accessibility Act

Business Value:
- Broader user base (15% of world population has disabilities)
- SEO improvement (search engines also text-based)
- Reduced legal risk
- Enhanced brand image
- Improved UX for all users

2. WCAG Guidelines

2.1 WCAG Principles (POUR)

┌─────────────────────────────────────────────────────────────────┐
                    WCAG 4 Principles                             
                                                                 
   P - Perceivable                                               
       Content must be perceivable by users                      
       - Alternative text                                        
       - Captions, audio descriptions                            
       - Color contrast                                          
                                                                 
   O - Operable                                                  
       UI components must be operable                            
       - Keyboard accessibility                                  
       - Sufficient time                                         
       - Seizure prevention                                      
                                                                 
   U - Understandable                                            
       Content must be understandable                            
       - Readable                                                
       - Predictable                                             
       - Input assistance                                        
                                                                 
   R - Robust                                                    
       Must be accessible with various technologies              
       - Compatibility                                           
       - Assistive technology support                            
└─────────────────────────────────────────────────────────────────┘

2.2 Compliance Levels

Level A (Required):
- Alternative text for images
- All functionality accessible via keyboard
- Limit flashing content

Level AA (Recommended - Most legal requirements):
- Color contrast 4.5:1 or higher
- Text resizable
- Consistent navigation
- Error identification and description

Level AAA (Highest):
- Color contrast 7:1 or higher
- Sign language interpretation
- All abbreviations explained

3. Semantic HTML

3.1 Using Semantic Elements

<!-- Bad example -->
<div class="header">
  <div class="nav">
    <div class="nav-item">Home</div>
    <div class="nav-item">About</div>
  </div>
</div>
<div class="main">
  <div class="article">
    <div class="title">Title</div>
    <div class="content">Content</div>
  </div>
</div>
<div class="footer">Footer</div>

<!-- Good example - Semantic HTML -->
<header>
  <nav aria-label="Main menu">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/about">About</a></li>
    </ul>
  </nav>
</header>
<main>
  <article>
    <h1>Title</h1>
    <p>Content</p>
  </article>
</main>
<footer>Footer</footer>

3.2 Heading Structure (Heading Hierarchy)

<!-- Correct heading hierarchy -->
<h1>Website Title</h1>
  <h2>Section 1</h2>
    <h3>Subsection 1.1</h3>
    <h3>Subsection 1.2</h3>
  <h2>Section 2</h2>
    <h3>Subsection 2.1</h3>
      <h4>Detail 2.1.1</h4>

<!-- Bad example - Skipping levels -->
<h1>Title</h1>
<h3>Don't skip to h3</h3>

<!-- Only one h1 per page -->

3.3 Image Accessibility

<!-- Informative image -->
<img src="chart.png" alt="Sales chart for 2024: Q1 $1M, Q2 $1.5M, Q3 $2M">

<!-- Decorative image (empty alt text) -->
<img src="decoration.png" alt="" role="presentation">

<!-- Complex image (provide long description) -->
<figure>
  <img src="complex-diagram.png" alt="System architecture diagram" aria-describedby="diagram-desc">
  <figcaption id="diagram-desc">
    This diagram shows data flow between client, web server, and database...
  </figcaption>
</figure>

<!-- Image in link -->
<a href="/products">
  <img src="product.jpg" alt="View new products">
</a>

3.4 Form Accessibility

<!-- Explicit label association -->
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>

<!-- Grouped form elements -->
<fieldset>
  <legend>Shipping Address</legend>

  <label for="street">Street Address:</label>
  <input type="text" id="street" name="street">

  <label for="city">City:</label>
  <input type="text" id="city" name="city">
</fieldset>

<!-- Connect error messages -->
<label for="password">Password:</label>
<input
  type="password"
  id="password"
  aria-describedby="password-error password-hint"
  aria-invalid="true"
>
<span id="password-hint">Must be at least 8 characters</span>
<span id="password-error" role="alert">Password is too short</span>

4. ARIA Attributes

4.1 ARIA Basic Concepts

┌─────────────────────────────────────────────────────────────────┐
                    ARIA Attribute Categories                     
                                                                 
   Roles:                                                        
   - Define element type/purpose                                
   - role="button", role="navigation", role="alert"            
                                                                 
   States:                                                       
   - Current state of element (changeable)                      
   - aria-expanded, aria-checked, aria-selected                
                                                                 
   Properties:                                                   
   - Element characteristics (usually fixed)                    
   - aria-label, aria-labelledby, aria-describedby             
                                                                 
   First Rule: Use native HTML when possible, don't use ARIA    
   Don't use <div role="button"> instead of <button>           
└─────────────────────────────────────────────────────────────────┘

4.2 Common ARIA Attributes

<!-- aria-label: Provide accessible name -->
<button aria-label="Close menu">
  <svg><!-- X icon --></svg>
</button>

<!-- aria-labelledby: Label with another element -->
<h2 id="section-title">Product List</h2>
<ul aria-labelledby="section-title">
  <li>Product 1</li>
  <li>Product 2</li>
</ul>

<!-- aria-describedby: Connect additional description -->
<input type="text" aria-describedby="name-help">
<p id="name-help">Enter your name in Korean</p>

<!-- aria-hidden: Hide from assistive technology -->
<span aria-hidden="true"></span> <!-- Decorative icon -->
<span class="sr-only">5 stars</span> <!-- For screen readers -->

<!-- aria-live: Announce dynamic content -->
<div aria-live="polite">New message arrived</div>
<div aria-live="assertive" role="alert">Error occurred!</div>

4.3 State Management

<!-- Expand/collapse state -->
<button
  aria-expanded="false"
  aria-controls="menu-content"
  id="menu-button"
>
  Menu
</button>
<div id="menu-content" hidden>
  <!-- Menu content -->
</div>

<script>
const button = document.getElementById('menu-button');
const content = document.getElementById('menu-content');

button.addEventListener('click', () => {
  const expanded = button.getAttribute('aria-expanded') === 'true';
  button.setAttribute('aria-expanded', !expanded);
  content.hidden = expanded;
});
</script>

<!-- Selection state -->
<ul role="listbox" aria-label="Color selection">
  <li role="option" aria-selected="true">Red</li>
  <li role="option" aria-selected="false">Blue</li>
  <li role="option" aria-selected="false">Green</li>
</ul>

<!-- Disabled state -->
<button aria-disabled="true">Cannot Submit</button>

4.4 Live Regions

<!-- Status message -->
<div role="status" aria-live="polite">
  3 items added to cart.
</div>

<!-- Alert message -->
<div role="alert" aria-live="assertive">
  Session expired. Please log in again.
</div>

<!-- Loading state -->
<div aria-busy="true" aria-live="polite">
  Loading data...
</div>

<!-- Polite vs Assertive -->
<!-- polite: Announce after current task completes (recommended) -->
<!-- assertive: Announce immediately (urgent only) -->

5. Keyboard Accessibility

5.1 Focus Management

<!-- Focusable elements -->
<!-- Auto: a[href], button, input, select, textarea -->

<!-- Using tabindex -->
<div tabindex="0">Focusable div</div>
<div tabindex="-1">Focusable only programmatically</div>
<!-- Avoid tabindex > 0 (confuses tab order) -->

<!-- Focus indicator styles -->
<style>
/* Don't remove default focus styles */
:focus {
  outline: 2px solid #4A90D9;
  outline-offset: 2px;
}

/* Hide focus ring on mouse click (optional) -->
:focus:not(:focus-visible) {
  outline: none;
}

/* Show only on keyboard focus */
:focus-visible {
  outline: 3px solid #4A90D9;
  outline-offset: 2px;
}
</style>

5.2 Keyboard Navigation Patterns

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

<style>
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  padding: 8px;
  background: #000;
  color: #fff;
  z-index: 100;
}
.skip-link:focus {
  top: 0;
}
</style>

<!-- Tab panel menu -->
<div role="tablist" aria-label="Product information">
  <button role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">
    Description
  </button>
  <button role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">
    Reviews
  </button>
</div>

<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
  Product description...
</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
  Reviews...
</div>

5.3 Focus Trap (Modal)

// Modal focus trap
function trapFocus(element) {
  const focusableElements = element.querySelectorAll(
    'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
  );
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

  element.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;

    if (e.shiftKey) {
      // Shift + Tab
      if (document.activeElement === firstElement) {
        lastElement.focus();
        e.preventDefault();
      }
    } else {
      // Tab
      if (document.activeElement === lastElement) {
        firstElement.focus();
        e.preventDefault();
      }
    }
  });

  // Focus first element
  firstElement.focus();
}

5.4 Keyboard Shortcuts

<!-- accesskey (use carefully) -->
<button accesskey="s">Save (Alt+S)</button>

<!-- Custom shortcuts implementation -->
<script>
document.addEventListener('keydown', (e) => {
  // Ctrl/Cmd + K for search
  if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
    e.preventDefault();
    document.getElementById('search').focus();
  }

  // Escape to close modal
  if (e.key === 'Escape') {
    closeModal();
  }
});
</script>

6. Testing and Tools

6.1 Automation Tools

# Lighthouse (built into Chrome DevTools)
# Measures Performance, Accessibility, SEO, etc.

# axe DevTools (browser extension)
npm install @axe-core/react  # For React projects

# Pa11y (CLI tool)
npm install -g pa11y
pa11y https://example.com

# eslint-plugin-jsx-a11y (React)
npm install eslint-plugin-jsx-a11y --save-dev

6.2 Manual Testing Checklist

┌─────────────────────────────────────────────────────────────────┐
│                 Manual Accessibility Testing Checklist           │
│                                                                 │
│ Keyboard Testing:                                               │
│ □ Tab key accesses all interactive elements                    │
│ □ Focus indicator clearly visible                              │
│ □ Logical tab order                                            │
│ □ No keyboard traps (except modals)                            │
│ □ Enter/Space activates buttons                                │
│ □ Escape closes popups/modals                                  │
│                                                                 │
│ Screen Reader Testing:                                          │
│ □ Appropriate image alternative text                           │
│ □ Logical heading structure                                    │
│ □ Form labels connected                                        │
│ □ Error messages recognized                                    │
│ □ Dynamic content announced                                    │
│                                                                 │
│ Visual Testing:                                                 │
│ □ Sufficient color contrast (4.5:1 or higher)                  │
│ □ Don't convey info by color alone                             │
│ □ Readable at 200% zoom                                        │
│ □ Animations controllable                                      │
└─────────────────────────────────────────────────────────────────┘

6.3 Screen Reader Testing

Major Screen Readers:
- NVDA (Windows, free)
- JAWS (Windows, paid)
- VoiceOver (macOS/iOS, built-in)
- TalkBack (Android, built-in)

VoiceOver Basic Commands (macOS):
- Cmd + F5: Toggle VoiceOver on/off
- Ctrl + Option + Arrow keys: Navigate
- Ctrl + Option + Space: Activate

NVDA Basic Commands (Windows):
- Insert + Space: Toggle NVDA mode
- Tab: Next focusable element
- H: Next heading
- B: Next button

7. Practice Problems

Exercise 1: Improve Image Accessibility

Improve accessibility of the following code.

<!-- Before -->
<img src="sale-banner.jpg">
<img src="icon-cart.png" onclick="addToCart()">

<!-- After (Example answer) -->
<img src="sale-banner.jpg" alt="Summer Sale - 30% off all items, until July 31">

<button type="button" onclick="addToCart()" aria-label="Add to cart">
  <img src="icon-cart.png" alt="">
</button>

Exercise 2: Improve Form Accessibility

Improve accessibility of the following form.

<!-- Before -->
<form>
  <input type="text" placeholder="Name">
  <input type="email" placeholder="Email">
  <div class="checkbox">
    <input type="checkbox"> Agree to terms
  </div>
  <button>Submit</button>
</form>

<!-- After (Example answer) -->
<form>
  <div>
    <label for="name">Name (Required)</label>
    <input type="text" id="name" name="name" required
           aria-describedby="name-help">
    <span id="name-help" class="help-text">Enter your full name</span>
  </div>

  <div>
    <label for="email">Email (Required)</label>
    <input type="email" id="email" name="email" required>
  </div>

  <div>
    <input type="checkbox" id="terms" name="terms" required>
    <label for="terms">
      I agree to the <a href="/terms">terms and conditions</a> (Required)
    </label>
  </div>

  <button type="submit">Submit</button>
</form>

Exercise 3: Implement Keyboard Accessibility

Add keyboard accessibility to a dropdown menu.

// Example answer
const dropdown = document.querySelector('.dropdown');
const button = dropdown.querySelector('button');
const menu = dropdown.querySelector('ul');
const items = menu.querySelectorAll('a');

button.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
    e.preventDefault();
    openMenu();
    items[0].focus();
  }
});

menu.addEventListener('keydown', (e) => {
  const currentIndex = Array.from(items).indexOf(document.activeElement);

  switch (e.key) {
    case 'ArrowDown':
      e.preventDefault();
      items[(currentIndex + 1) % items.length].focus();
      break;
    case 'ArrowUp':
      e.preventDefault();
      items[(currentIndex - 1 + items.length) % items.length].focus();
      break;
    case 'Escape':
      closeMenu();
      button.focus();
      break;
  }
});

Next Steps

References

to navigate between lessons