general2/1/20247 min read

Web Accessibility Guide

Comprehensive guide to building accessible web applications that work for everyone

accessibilitya11ywcaginclusive-designsemantic-html
By Zen Frontend

Web Accessibility Guide

Web accessibility ensures that websites and applications are usable by people with disabilities. It's not just a legal requirement—it's a fundamental aspect of good user experience design.

What is Web Accessibility?

Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. This includes:

  • Visual disabilities: Blindness, low vision, color blindness
  • Motor disabilities: Limited fine motor control, paralysis
  • Cognitive disabilities: Learning disabilities, attention disorders
  • Auditory disabilities: Deafness, hard of hearing

The Four Principles of Accessibility (POUR)

1. Perceivable

Information and UI components must be presentable in ways users can perceive.

2. Operable

UI components and navigation must be operable by all users.

3. Understandable

Information and UI operation must be understandable.

4. Robust

Content must be robust enough to be interpreted by assistive technologies.

Semantic HTML

Use Proper HTML Elements

<!-- ❌ Bad - Non-semantic -->
<div class="button" onclick="submit()">Submit</div>
<div class="heading">Main Title</div>

<!-- ✅ Good - Semantic -->
<button onclick="submit()">Submit</button>
<h1>Main Title</h1>

Headings Structure

<!-- ✅ Good - Proper heading hierarchy -->
<h1>Page Title</h1>
  <h2>Section Title</h2>
    <h3>Subsection Title</h3>
    <h3>Another Subsection</h3>
  <h2>Another Section</h2>
    <h3>Subsection Title</h3>

Lists and Navigation

<!-- ✅ Good - Proper list structure -->
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/home">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

<!-- ✅ Good - Description lists -->
<dl>
  <dt>Name</dt>
  <dd>John Doe</dd>
  <dt>Email</dt>
  <dd>john@example.com</dd>
</dl>

ARIA (Accessible Rich Internet Applications)

ARIA Labels and Descriptions

<!-- ✅ Good - ARIA labels -->
<button aria-label="Close dialog">×</button>
<input type="text" aria-label="Search products" placeholder="Search..." />

<!-- ✅ Good - ARIA descriptions -->
<input 
  type="password" 
  aria-describedby="password-help"
  aria-required="true"
/>
<div id="password-help">
  Password must be at least 8 characters long
</div>

ARIA States and Properties

<!-- ✅ Good - ARIA states -->
<button aria-expanded="false" aria-controls="menu">
  Menu
</button>
<ul id="menu" aria-hidden="true">
  <li><a href="/item1">Item 1</a></li>
  <li><a href="/item2">Item 2</a></li>
</ul>

<!-- ✅ Good - ARIA properties -->
<div role="tablist" aria-label="Product features">
  <button 
    role="tab" 
    aria-selected="true" 
    aria-controls="panel1"
    id="tab1"
  >
    Features
  </button>
  <div 
    role="tabpanel" 
    aria-labelledby="tab1" 
    id="panel1"
  >
    Product features content
  </div>
</div>

Live Regions

<!-- ✅ Good - Live regions for dynamic content -->
<div aria-live="polite" aria-atomic="true">
  <!-- Screen readers will announce changes here -->
</div>

<div aria-live="assertive" aria-atomic="false">
  <!-- Urgent updates announced immediately -->
</div>

Keyboard Navigation

Focus Management

/* ✅ Good - Visible focus indicators */
button:focus,
input:focus,
a:focus {
  outline: 2px solid #007bff;
  outline-offset: 2px;
}

/* Custom focus styles */
.custom-button:focus {
  box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);
}

Tab Order

<!-- ✅ Good - Logical tab order -->
<form>
  <label for="name">Name:</label>
  <input type="text" id="name" />
  
  <label for="email">Email:</label>
  <input type="email" id="email" />
  
  <button type="submit">Submit</button>
</form>

Skip Links

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

<main id="main-content">
  <!-- Main content here -->
</main>
.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;
}

Color and Contrast

Color Contrast Ratios

  • Normal text: 4.5:1 minimum
  • Large text: 3:1 minimum
  • UI components: 3:1 minimum
/* ✅ Good - Sufficient contrast */
.text-primary {
  color: #000000; /* Black on white = 21:1 */
  background-color: #ffffff;
}

.text-secondary {
  color: #333333; /* Dark gray on white = 12.6:1 */
  background-color: #ffffff;
}

Don't Rely on Color Alone

<!-- ❌ Bad - Color only -->
<span style="color: red;">Error: Invalid input</span>

<!-- ✅ Good - Color + icon + text -->
<span style="color: red;" aria-label="Error">
  <span aria-hidden="true">⚠️</span>
  Error: Invalid input
</span>

Images and Media

Alt Text

<!-- ✅ Good - Descriptive alt text -->
<img 
  src="chart.jpg" 
  alt="Bar chart showing sales increased 25% from Q1 to Q2"
/>

<!-- ✅ Good - Decorative images -->
<img src="decoration.jpg" alt="" role="presentation" />

<!-- ✅ Good - Complex images -->
<img src="infographic.jpg" alt="Sales infographic" />
<p>
  <a href="infographic-text.html">Read the text version of this infographic</a>
</p>

Video and Audio

<!-- ✅ Good - Video with captions -->
<video controls>
  <source src="video.mp4" type="video/mp4">
  <track 
    kind="captions" 
    src="captions.vtt" 
    srclang="en" 
    label="English"
    default
  >
  <p>Your browser doesn't support video. 
    <a href="video.mp4">Download the video</a>
  </p>
</video>

<!-- ✅ Good - Audio with transcript -->
<audio controls>
  <source src="audio.mp3" type="audio/mpeg">
  <p>Your browser doesn't support audio. 
    <a href="transcript.html">Read the transcript</a>
  </p>
</audio>

Forms and Inputs

Labels and Descriptions

<!-- ✅ Good - Proper form structure -->
<form>
  <fieldset>
    <legend>Contact Information</legend>
    
    <div>
      <label for="name">Full Name *</label>
      <input 
        type="text" 
        id="name" 
        name="name" 
        required
        aria-describedby="name-help"
      />
      <div id="name-help">Enter your first and last name</div>
    </div>
    
    <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" aria-live="polite">
        <!-- Error messages appear here -->
      </div>
    </div>
  </fieldset>
  
  <button type="submit">Submit</button>
</form>

Error Handling

// ✅ Good - Accessible error handling
function validateForm() {
  const email = document.getElementById('email');
  const errorDiv = document.getElementById('email-error');
  
  if (!email.validity.valid) {
    email.setAttribute('aria-invalid', 'true');
    errorDiv.textContent = 'Please enter a valid email address';
    errorDiv.setAttribute('role', 'alert');
  } else {
    email.setAttribute('aria-invalid', 'false');
    errorDiv.textContent = '';
  }
}

Testing Accessibility

Automated Testing

// Using axe-core
import { axe, toHaveNoViolations } from 'jest-axe';

expect.extend(toHaveNoViolations);

test('should not have accessibility violations', async () => {
  const { container } = render(<MyComponent />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

Manual Testing

  1. Keyboard Navigation

    • Tab through all interactive elements
    • Use Enter/Space to activate buttons
    • Use arrow keys for menus and lists
  2. Screen Reader Testing

    • Test with NVDA (Windows)
    • Test with VoiceOver (macOS)
    • Test with JAWS (Windows)
  3. Color and Contrast

    • Use browser dev tools
    • Test with color blindness simulators
    • Verify contrast ratios

Browser DevTools

// Check for accessibility issues
// Chrome DevTools > Lighthouse > Accessibility

// Test keyboard navigation
// Tab through the page and verify focus indicators

// Test with reduced motion
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Common Accessibility Patterns

Modal Dialogs

<!-- ✅ Good - Accessible modal -->
<div 
  role="dialog" 
  aria-modal="true" 
  aria-labelledby="modal-title"
  aria-describedby="modal-description"
>
  <h2 id="modal-title">Confirm Action</h2>
  <p id="modal-description">
    Are you sure you want to delete this item?
  </p>
  <button onclick="closeModal()">Cancel</button>
  <button onclick="confirmAction()">Delete</button>
</div>

Tabs

<!-- ✅ Good - Accessible tabs -->
<div role="tablist" aria-label="Product information">
  <button 
    role="tab" 
    aria-selected="true" 
    aria-controls="panel1"
    id="tab1"
    tabindex="0"
  >
    Description
  </button>
  <button 
    role="tab" 
    aria-selected="false" 
    aria-controls="panel2"
    id="tab2"
    tabindex="-1"
  >
    Specifications
  </button>
</div>

<div 
  role="tabpanel" 
  aria-labelledby="tab1" 
  id="panel1"
>
  Product description content
</div>

Key Concepts

  1. Semantic HTML: Use proper HTML elements for structure
  2. ARIA: Enhance accessibility with ARIA attributes
  3. Keyboard Navigation: Ensure all functionality is keyboard accessible
  4. Color and Contrast: Meet WCAG contrast requirements
  5. Testing: Use both automated and manual testing methods

Common Interview Questions

  • What are the four principles of accessibility?
  • How do you make a website keyboard accessible?
  • What's the difference between aria-label and aria-labelledby?
  • How do you test for accessibility issues?
  • What are the WCAG contrast requirements?

Related Topics

  • Web Vitals and Performance
  • Semantic HTML
  • Performance Optimization
  • User Experience Design
  • SEO Best Practices