TopFrontDev logo
HomeVideosPlaylistsResourcesArticlesAbout
Youtube logoSubscribe

TopFrontDev

Front-end techniques that actually ship. Practical engineering you can use in production.

Content

  • Videos
  • Playlists
  • Articles
  • Resources

About

  • About
  • Sponsorship
  • Contact

Connect

Youtube logogithub logolinkedin logo

© 2026 TopFrontDev. All rights reserved.

TopFrontDev logo
HomeVideosPlaylistsResourcesArticlesAbout
Youtube logoSubscribe

The Complete Accessibility Checklist for Web Apps

October 10, 2025

Building accessible web applications ensures everyone can use your product. This comprehensive WCAG 2.2 compliant checklist covers essential accessibility requirements for modern web development.

WCAG 2.2 Overview

WCAG 2.2 builds on WCAG 2.1 with new success criteria addressing:

  • Focus Not Obscured - Focus indicators must not be hidden by other content
  • Target Size (Minimum) - Interactive elements need adequate touch/click targets
  • Dragging Movements - Dragging interactions must have single-pointer alternatives
  • Accessible Authentication - Alternatives to cognitive function tests
  • Consistent Help - Help information must be consistently located
  • Redundant Entry - Avoid re-entering information across steps

Semantic HTML & ARIA Landmarks

Use semantic HTML elements and ARIA landmarks for screen reader navigation:

<!-- Primary landmarks -->
<header>
  <nav aria-label="Main navigation">
    <ul>
      <li><a href="/">Home</a></li>
      <li><a href="/products">Products</a></li>
    </ul>
  </nav>
</header>

<main>
  <article>
    <h1>Article Title</h1>
    <section aria-labelledby="overview">
      <h2 id="overview">Overview</h2>
      <p>Content...</p>
    </section>
  </article>
</main>

<aside aria-label="Related content">
  <!-- Sidebar content -->
</aside>

<footer>
  <p>Copyright 2024</p>
</footer>

<!-- Avoid generic divs -->
<!-- ❌ Bad -->
<div class="header">
  <div class="nav">
    <div class="link">Home</div>
  </div>
</div>

Keyboard Navigation & Focus Management

All interactive elements must be keyboard accessible with visible focus indicators:

// Custom button with proper keyboard support
function CustomButton({ onClick, children }) {
  const handleKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      onClick(e);
    }
  };

  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={handleKeyDown}
      className="custom-button"
    >
      {children}
    </div>
  );
}

// Focus management for modals (WCAG 2.2 Focus Not Obscured)
function Modal({ isOpen, onClose, children }) {
  const dialogRef = useRef(null);
  const previousFocus = useRef(null);

  useEffect(() => {
    if (isOpen) {
      // Store previous focus
      previousFocus.current = document.activeElement;

      // Focus dialog
      dialogRef.current?.focus();

      // Trap focus within modal
      const focusableElements = dialogRef.current?.querySelectorAll(
        'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
      );

      if (focusableElements?.length) {
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];

        const handleTabKey = (e) => {
          if (e.key === 'Tab') {
            if (e.shiftKey) {
              if (document.activeElement === firstElement) {
                e.preventDefault();
                lastElement.focus();
              }
            } else {
              if (document.activeElement === lastElement) {
                e.preventDefault();
                firstElement.focus();
              }
            }
          }
        };

        document.addEventListener('keydown', handleTabKey);

        return () => {
          document.removeEventListener('keydown', handleTabKey);
          // Restore focus on close
          previousFocus.current?.focus();
        };
      }
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      ref={dialogRef}
      role="dialog"
      aria-modal="true"
      tabIndex={-1}
      className="modal"
    >
      {children}
      <button onClick={onClose} aria-label="Close dialog">×</button>
    </div>
  );
}

Target Size (WCAG 2.2 New)

Interactive elements must be at least 24×24 CSS pixels (44×44px recommended):

/* Minimum target size */
button, [role="button"], input, select, textarea {
  min-width: 44px;
  min-height: 44px;
}

/* For smaller elements, add padding */
.small-button {
  padding: 8px; /* Total 40px with border */
  min-width: 24px;
  min-height: 24px;
}

ARIA Labels, Roles & States

Use ARIA strategically - prefer semantic HTML over ARIA when possible:

<!-- Screen reader text (visually hidden) -->
<a href="#main" class="sr-only focusable">Skip to main content</a>
<main id="main">...</main>

<!-- Icon buttons need aria-label -->
<button aria-label="Close dialog" class="close-button">
  <svg aria-hidden="true"><!-- icon --></svg>
</button>

<!-- Status messages -->
<div role="status" aria-live="polite" aria-atomic="true">
  File uploaded successfully!
</div>

<!-- Loading states -->
<div aria-live="polite" aria-busy="true" role="status">
  <div aria-hidden="true">Loading...</div>
  <progress value="50" max="100" aria-label="Loading progress">50%</progress>
</div>

<!-- Complex widgets -->
<div role="tablist" aria-label="Product categories">
  <button role="tab" aria-selected="true" aria-controls="electronics">
    Electronics
  </button>
  <div role="tabpanel" id="electronics" aria-labelledby="electronics-tab">
    <!-- Content -->
  </div>
</div>

<!-- Form validation -->
<input
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
  required
/>
<div id="email-error" role="alert">
  Please enter a valid email address
</div>

Focus Appearance (WCAG 2.2 AAA)

Focus indicators must be clearly visible and meet contrast requirements:

/* High contrast focus indicators */
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

/* Alternative: border-based focus */
.focusable:focus-visible {
  border: 2px solid #005fcc;
  box-shadow: 0 0 0 1px #005fcc;
}

Focus Management

function Dialog({ isOpen, onClose, children }) {
  const dialogRef = useRef(null);
  const previousFocus = useRef(null);

  useEffect(() => {
    if (isOpen) {
      // Store previous focus
      previousFocus.current = document.activeElement;

      // Focus dialog
      dialogRef.current?.focus();

      return () => {
        // Restore focus on close
        previousFocus.current?.focus();
      };
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div
      ref={dialogRef}
      role="dialog"
      aria-modal="true"
      tabIndex={-1}
    >
      {children}
      <button onClick={onClose}>Close</button>
    </div>
  );
}

Color Contrast

WCAG AA requires:

  • Normal text: 4.5:1 contrast ratio
  • Large text (18pt+): 3:1 contrast ratio
/* Good - 7:1 ratio */
.text {
  color: #000000;
  background: #ffffff;
}

/* Bad - 2:1 ratio */
.text {
  color: #cccccc;
  background: #ffffff;
}

Form Accessibility & Authentication

Forms must be fully accessible with proper labeling and validation:

<form action="/api/contact" method="post">
  <!-- Explicit labels -->
  <div>
    <label for="name">Full Name</label>
    <input
      type="text"
      id="name"
      name="name"
      autocomplete="name"
      required
      aria-describedby="name-hint"
    />
    <span id="name-hint">Enter your full legal name</span>
  </div>

  <!-- Group related fields -->
  <fieldset>
    <legend>Contact Preference</legend>

    <input
      type="radio"
      id="email-pref"
      name="contact-method"
      value="email"
      checked
    />
    <label for="email-pref">Email</label>

    <input
      type="radio"
      id="phone-pref"
      name="contact-method"
      value="phone"
    />
    <label for="phone-pref">Phone</label>
  </fieldset>

  <!-- Validation feedback -->
  <div aria-live="polite">
    <span id="email-error" role="alert" aria-atomic="true">
      <!-- Error message inserted here -->
    </span>
  </div>

  <!-- Submit button -->
  <button type="submit" aria-describedby="submit-help">
    Send Message
  </button>
  <div id="submit-help">
    Your information will be kept confidential
  </div>
</form>

Accessible Authentication (WCAG 2.2 New)

Provide alternatives to cognitive function tests:

<!-- Situation A: Provide alternatives to memory tests -->
<div class="auth-options">
  <h3>Choose your authentication method:</h3>

  <button onclick="usePassword()">
    Use Password
  </button>

  <button onclick="useBiometric()">
    Use Biometric (Fingerprint/Face)
  </button>

  <button onclick="useWebAuthn()">
    Use Security Key
  </button>

  <!-- Avoid: Pure memory/cognitive tests -->
  <!-- ❌ Bad: "What was the name of your first pet?" -->
</div>

<!-- Situation B: Object recognition -->
<div class="captcha-alternative">
  <p>Select all images containing traffic lights:</p>
  <div class="image-grid" role="group" aria-label="Select traffic lights">
    <!-- Images with proper alt text and keyboard support -->
  </div>
</div>

Dragging Movements (WCAG 2.2 New)

Provide single-pointer alternatives for drag operations:

<!-- Draggable list with keyboard support -->
<div class="sortable-list">
  <div class="list-item" draggable="true">
    <span>Item 1</span>
    <button aria-label="Move up">↑</button>
    <button aria-label="Move down">↓</button>
    <button aria-label="Remove">×</button>
  </div>
</div>

<!-- Drag alternative: explicit controls -->
<div class="reorder-controls">
  <select aria-label="Move item to position">
    <option>Move to top</option>
    <option>Move up one</option>
    <option>Move down one</option>
    <option>Move to bottom</option>
  </select>
</div>

Images and Media

<!-- Meaningful images -->
<img src="chart.png" alt="Sales increased 50% in Q4 2024" />

<!-- Decorative images -->
<img src="decorative.png" alt="" role="presentation" />

<!-- Video captions -->
<video>
  <source src="video.mp4" />
  <track kind="captions" src="captions.vtt" srclang="en" label="English" />
</video>

Media & Dynamic Content

Ensure all media and dynamic content is accessible:

<!-- Images with meaningful alt text -->
<img
  src="chart.png"
  alt="Sales increased 50% in Q4 2024, with mobile sales up 75%"
  width="400"
  height="300"
/>

<!-- Decorative images -->
<img
  src="decorative-pattern.png"
  alt=""
  role="presentation"
/>

<!-- Complex images need detailed descriptions -->
<figure>
  <img src="diagram.png" alt="User authentication flow diagram" />
  <figcaption>
    Figure 1: Authentication flow showing login, 2FA, and session creation
  </figcaption>
  <details>
    <summary>Long description</summary>
    <p>The diagram shows a user icon connecting to a login form...</p>
  </details>
</figure>

<!-- Video with captions and transcripts -->
<video controls preload="metadata">
  <source src="tutorial.mp4" type="video/mp4" />
  <track
    kind="captions"
    src="tutorial-captions.vtt"
    srclang="en"
    label="English"
    default
  />
  <track
    kind="subtitles"
    src="tutorial-subtitles-es.vtt"
    srclang="es"
    label="Español"
  />
  <!-- Fallback -->
  <p>Your browser doesn't support video. <a href="tutorial.mp4">Download</a></p>
</video>

<!-- Provide transcript -->
<details>
  <summary>Video Transcript</summary>
  <p>[00:00] Welcome to our accessibility tutorial...</p>
</details>

Comprehensive Testing Checklist

Automated Testing

  • axe DevTools - Run automated accessibility audits
  • Lighthouse - Check accessibility score (target: 90+)
  • WAVE - Web accessibility evaluation
  • ARC Toolkit - Comprehensive accessibility testing
  • eslint-plugin-jsx-a11y - Lint React components

Manual Testing

  • Keyboard Navigation - Tab through all interactive elements
  • Screen Readers - Test with NVDA, JAWS, VoiceOver, TalkBack
  • Focus Management - Verify focus indicators and tab order
  • Zoom Testing - 200% zoom, 400% zoom
  • Color Contrast - Use contrast checkers (4.5:1 minimum)
  • Touch Targets - Minimum 44×44px for touch interfaces
  • Reduced Motion - Test prefers-reduced-motion settings

WCAG 2.2 Specific Tests

  • Focus Not Obscured - Focus indicators never hidden
  • Target Size - All interactive elements meet minimum size
  • Dragging Movements - Single-pointer alternatives provided
  • Accessible Authentication - No pure cognitive tests
  • Consistent Help - Help information consistently located

User Testing

  • Real User Testing - Include users with disabilities
  • Assistive Technology - Test with various screen readers
  • Mobile Accessibility - Test with TalkBack, VoiceOver on mobile
  • Cognitive Accessibility - Test with users who need extra time

Modern Accessibility Tools (2024)

Development Tools

  • axe-core - JavaScript accessibility testing library
  • @axe-core/playwright - Automated testing in Playwright
  • cypress-axe - Cypress accessibility testing
  • jest-axe - Unit test accessibility assertions
  • storybook-addon-a11y - Storybook accessibility testing

Design Tools

  • Stark - Contrast checking and accessibility design
  • Color Contrast Analyzer - Adobe/Apple contrast tools
  • Accessibility Insights - Microsoft accessibility tools
  • WAVE Browser Extension - Real-time accessibility feedback

Screen Readers & Testing

  • NVDA (Windows) - Free, open-source screen reader
  • JAWS (Windows) - Professional screen reader
  • VoiceOver (macOS/iOS) - Built-in Apple screen reader
  • TalkBack (Android) - Google screen reader
  • Narrator (Windows) - Microsoft screen reader

CI/CD Integration

# GitHub Actions accessibility testing
- name: Accessibility Tests
  run: |
    npx axe-core --url https://your-site.com
    npx lighthouse --accessibility https://your-site.com

- name: Visual Regression
  run: |
    npx playwright test --grep "accessibility"

Progressive Enhancement Strategy

Build accessible by default, enhance progressively:

// Feature detection for enhanced interactions
const supportsIntersectionObserver = 'IntersectionObserver' in window;

if (supportsIntersectionObserver) {
  // Enhanced lazy loading
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        loadContent(entry.target);
      }
    });
  });
} else {
  // Fallback: load all content
  loadAllContent();
}

// Safe keyboard event handling
function handleKeyPress(event) {
  // Always prevent default for custom behavior
  switch (event.key) {
    case 'Enter':
    case ' ': // Space
      event.preventDefault();
      performAction();
      break;
    case 'Escape':
      event.preventDefault();
      closeModal();
      break;
  }
}

Legal & Compliance Considerations

WCAG Conformance Levels

  • A - Basic accessibility (minimum legal requirement)
  • AA - Enhanced accessibility (recommended for most sites)
  • AAA - Highest accessibility (required for government/mission-critical)

Industry Standards

  • Section 508 (US Government)
  • EN 301 549 (European accessibility standard)
  • AODA (Ontario Accessibility for Ontarians with Disabilities Act)
  • DDA (UK Disability Discrimination Act)

Documentation Requirements

  • Accessibility Statement
  • Accessibility Conformance Report (ACR)
  • Feedback/Contact Information for accessibility issues
  • Regular accessibility audits (annual minimum)

Resources & Learning

Official Standards

  • WCAG 2.2 Guidelines - Latest accessibility standard
  • ARIA Authoring Practices Guide - ARIA implementation patterns
  • WAI-ARIA Specification - ARIA technical specification

Learning Resources

  • WebAIM - Comprehensive accessibility articles
  • Deque University - Free accessibility courses
  • Accessibility Guidelines Working Group - WCAG development
  • A11y Project - Practical accessibility resources

Communities

  • WAI Interest Group - Accessibility discussions
  • CSS Accessibility Community Group
  • Web Accessibility Initiative - W3C accessibility resources

Conclusion

Accessibility is not a checkbox - it's an ongoing commitment to inclusive design. WCAG 2.2 brings important updates that address modern user needs, from mobile accessibility to cognitive load reduction.

Start with semantic HTML, proper keyboard navigation, and automated testing. Then progressively enhance with ARIA, advanced focus management, and user testing. Remember: the best accessibility happens when accessibility is designed in from the start, not bolted on at the end.

Your users will thank you for creating experiences that work for everyone, regardless of ability. What accessibility challenge are you tackling next?

TopFrontDev

Front-end techniques that actually ship. Practical engineering you can use in production.

Content

  • Videos
  • Playlists
  • Articles
  • Resources

About

  • About
  • Sponsorship
  • Contact

Connect

Youtube logogithub logolinkedin logo

© 2026 TopFrontDev. All rights reserved.