Web Accessibility: A Developer's Guide

MG

Maria Garcia

December 20, 2023
15 min read
Web Accessibility: A Developer's Guide
AccessibilityWeb DevelopmentUXInclusive Design

Web Accessibility: A Developer's Guide

Web accessibility ensures that websites and applications are usable by everyone, including people with disabilities. It's not just about compliance—it's about creating inclusive experiences that benefit all users.

Why Accessibility Matters

The Numbers

  • 1 billion people worldwide live with some form of disability
  • 15% of the global population experiences some form of disability
  • $13 trillion in annual disposable income controlled by people with disabilities
  • ADA: Americans with Disabilities Act
  • Section 508: US federal accessibility requirements
  • WCAG: Web Content Accessibility Guidelines (international standard)
  • EN 301 549: European accessibility standard

WCAG 2.1 Principles

1. Perceivable

Information must be presentable in ways users can perceive:

text
<!-- Good: Alt text for images -->
<img src="chart.png" alt="Sales increased 25% from Q1 to Q2" />

<!-- Good: Captions for videos -->
<video controls>
  <source src="video.mp4" type="video/mp4" />
  <track kind="captions" src="captions.vtt" srclang="en" label="English" />
</video>

2. Operable

Interface components must be operable:

text
<!-- Good: Keyboard accessible -->
<button onclick="toggleMenu()" onkeydown="handleKeyDown(event)">
  Menu
</button>

<!-- Good: Focus management -->
<div tabindex="0" role="button" aria-pressed="false">
  Custom Button
</div>

3. Understandable

Information and UI operation must be understandable:

text
<!-- Good: Clear labels -->
<label for="email">Email Address (required)</label>
<input type="email" id="email" required aria-describedby="email-error" />
<div id="email-error" role="alert">Please enter a valid email address</div>

4. Robust

Content must be robust enough for various assistive technologies:

text
<!-- Good: Semantic HTML -->
<nav aria-label="Main navigation">
  <ul>
    <li><a href="/" aria-current="page">Home</a></li>
    <li><a href="/about">About</a></li>
    <li><a href="/contact">Contact</a></li>
  </ul>
</nav>

Common Accessibility Issues

1. Missing Alt Text

text
<!-- Bad -->
<img src="product.jpg" />

<!-- Good -->
<img src="product.jpg" alt="Red leather handbag with gold hardware" />

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

2. Poor Color Contrast

text
/* Bad: Insufficient contrast */
.text {
  color: #999;
  background: #fff; /* Contrast ratio: 2.85:1 */
}

/* Good: Sufficient contrast */
.text {
  color: #666;
  background: #fff; /* Contrast ratio: 5.74:1 */
}

3. Keyboard Navigation Issues

text
// Bad: Not keyboard accessible
<div onClick={handleClick}>Click me</div>

// Good: Keyboard accessible
<button onClick={handleClick} onKeyDown={handleKeyDown}>
  Click me
</button>

// Good: Custom interactive element
<div
  role="button"
  tabIndex={0}
  onClick={handleClick}
  onKeyDown={(e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      handleClick();
    }
  }}
>
  Custom Button
</div>

ARIA (Accessible Rich Internet Applications)

Roles

text
<div role="alert">Error: Please fill in all required fields</div>
<div role="tabpanel" aria-labelledby="tab1">Tab content</div>
<div role="progressbar" aria-valuenow="32" aria-valuemin="0" aria-valuemax="100">
  32% complete
</div>

Properties

text
<button aria-expanded="false" aria-controls="menu">Menu</button>
<input aria-required="true" aria-describedby="password-help" />
<div id="password-help">Password must be at least 8 characters</div>

States

text
<button aria-pressed="true">Bold</button>
<input aria-invalid="true" aria-describedby="error" />
<div aria-hidden="true">Decorative content</div>

Testing for Accessibility

Automated Testing

text
// 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
  2. Screen Reader: Test with NVDA, JAWS, or VoiceOver
  3. Color Contrast: Use tools like WebAIM's contrast checker
  4. Zoom: Test at 200% zoom level
  5. Focus Management: Ensure focus is visible and logical

Tools

  • axe DevTools: Browser extension for accessibility testing
  • Lighthouse: Built-in Chrome accessibility audit
  • WAVE: Web accessibility evaluation tool
  • Color Oracle: Color blindness simulator

React Accessibility Patterns

Focus Management

text
import { useRef, useEffect } from 'react';

function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);
  const previousFocus = useRef(null);

  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement;
      modalRef.current?.focus();
    } else {
      previousFocus.current?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      ref={modalRef}
      role="dialog"
      aria-modal="true"
      tabIndex={-1}
      onKeyDown={(e) => {
        if (e.key === 'Escape') onClose();
      }}
    >
      {children}
    </div>
  );
}

Form Accessibility

text
function AccessibleForm() {
  const [errors, setErrors] = useState({});

  return (
    <form>
      <fieldset>
        <legend>Personal Information</legend>
        
        <div>
          <label htmlFor="firstName">
            First Name {errors.firstName && <span aria-label="required">*</span>}
          </label>
          <input
            id="firstName"
            type="text"
            required
            aria-invalid={!!errors.firstName}
            aria-describedby={errors.firstName ? "firstName-error" : undefined}
          />
          {errors.firstName && (
            <div id="firstName-error" role="alert">
              {errors.firstName}
            </div>
          )}
        </div>
      </fieldset>
    </form>
  );
}

Performance and Accessibility

Reduced Motion

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

High Contrast Mode

text
@media (prefers-contrast: high) {
  .button {
    border: 2px solid;
    background: ButtonFace;
    color: ButtonText;
  }
}

Building an Accessibility Culture

Team Practices

  1. Include accessibility in design reviews
  2. Add accessibility testing to CI/CD
  3. Provide accessibility training
  4. Use accessibility linting tools
  5. Test with real users

Documentation

  • Document accessibility features
  • Maintain an accessibility checklist
  • Create component accessibility guidelines
  • Share accessibility resources

Conclusion

Web accessibility is not a feature—it's a fundamental aspect of good web development. By following WCAG guidelines, using semantic HTML, implementing ARIA correctly, and testing thoroughly, we can create web experiences that truly work for everyone.

Accessibility benefits everyone: what helps users with disabilities often improves the experience for all users.

Read Next

Optimizing React Performance: Advanced Techniques
2 Jan 2024
9 min read

Deep dive into advanced React performance optimization techniques for large-scale applications.

The Future of Web Development: Server Components
10 Jan 2024
6 min read

Exploring React Server Components and how they're changing the way we think about client-server boundaries.

WebAssembly: The Future of High-Performance Web Apps
21 Dec 2023
10 min read

Explore WebAssembly (WASM) and how it enables near-native performance for web applications.