Web Development

Web Accessibility (WCAG): A Developer’s Checklist

Web Accessibility (WCAG): A Developer’s Checklist

Web accessibility means building websites and applications that everyone can use, regardless of ability. Over one billion people worldwide live with some form of disability — visual, auditory, motor, or cognitive. Beyond the moral imperative, accessibility is increasingly a legal requirement. The ADA (Americans with Disabilities Act), the European Accessibility Act, and similar legislation in other jurisdictions mandate that digital services be accessible to people with disabilities.

WCAG (Web Content Accessibility Guidelines), published by the W3C, provides the technical standard. WCAG 2.2, released in October 2023, is the current version. It defines testable success criteria organized into three conformance levels: A (minimum), AA (standard target for most websites), and AAA (highest). Most legal requirements and organizational policies target WCAG 2.1 Level AA compliance.

The Four Principles: POUR

WCAG organizes its guidelines around four foundational principles. Every accessibility requirement traces back to one of these:

Perceivable

All information and UI components must be presentable to users in ways they can perceive. This means providing text alternatives for images, captions for video, and ensuring content is not conveyed through a single sensory channel (like color alone). A user who cannot see an image needs alt text. A user who cannot hear audio needs captions. A user who cannot distinguish red from green needs an icon or label alongside color indicators.

Operable

Users must be able to navigate and interact with all functionality. This covers keyboard accessibility, sufficient time to complete tasks, seizure prevention (no flashing content), and clear navigation mechanisms. Every interactive element must be reachable and operable without a mouse.

Understandable

Content and UI behavior must be understandable. This means using clear language, providing predictable navigation patterns, helping users avoid and correct errors, and labeling form controls clearly. A user should never be confused about what a control does or where they are in the application.

Robust

Content must work reliably with current and future technologies, including assistive technologies like screen readers, magnifiers, and voice control software. This is achieved through valid HTML, proper use of ARIA attributes, and adherence to established web standards.

Semantic HTML: The Foundation

The single most impactful thing you can do for accessibility is use HTML elements according to their intended purpose. Screen readers and other assistive technologies rely on the semantic structure of HTML to convey meaning and navigation options to users.

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

<main>
  <article>
    <h1>Article Title</h1>
    <p>Content goes here...</p>

    <section>
      <h2>Section Heading</h2>
      <p>Section content...</p>
    </section>
  </article>

  <aside aria-label="Related articles">
    <h2>Related Articles</h2>
    <ul>
      <li><a href="/post-2">Related Post</a></li>
    </ul>
  </aside>
</main>

<footer>
  <p>Copyright 2026</p>
</footer>
<!-- Incorrect: div soup with no semantic meaning -->
<div class="header">
  <div class="nav">
    <div class="nav-item"><a href="/">Home</a></div>
  </div>
</div>
<div class="main">
  <div class="title">Article Title</div>
  <div class="text">Content goes here...</div>
</div>

Landmarks like <header>, <nav>, <main>, <aside>, and <footer> create a navigation map that screen reader users can browse directly. Proper heading hierarchy (<h1> through <h6>) provides an outline of the page content. Using <button> instead of <div onclick> gives you keyboard handling and screen reader announcements for free.

Images and Alternative Text

Every image needs appropriate alternative text. The type of alt text depends on the image’s purpose:

<!-- Informative image: describe what it conveys -->
<img src="chart.png" alt="Revenue grew 40% in Q3, reaching $2.4 million">

<!-- Functional image (link/button): describe the action -->
<a href="/search">
  <img src="search-icon.svg" alt="Search">
</a>

<!-- Decorative image: empty alt, role=presentation -->
<img src="decorative-border.svg" alt="" role="presentation">

<!-- Complex image: detailed description -->
<figure>
  <img src="architecture-diagram.png"
       alt="System architecture showing three microservices connected through an API gateway"
       aria-describedby="arch-desc">
  <figcaption id="arch-desc">
    The user service handles authentication, the product service manages
    inventory, and the order service processes transactions. All three
    communicate through the API gateway, which handles routing and rate limiting.
  </figcaption>
</figure>

Keyboard Navigation

Many users navigate websites entirely with a keyboard — people with motor disabilities, power users, and screen reader users. Every interactive element must be keyboard accessible.

Focus Management

/* Never remove focus outlines without a replacement */
/* BAD: */
*:focus { outline: none; }

/* GOOD: Custom focus styles */
*:focus-visible {
  outline: 3px solid #c2724e;
  outline-offset: 2px;
  border-radius: 2px;
}

/* Hide focus ring for mouse users, show for keyboard users */
button:focus:not(:focus-visible) {
  outline: none;
}

button:focus-visible {
  outline: 3px solid #c2724e;
  outline-offset: 2px;
}

Skip Navigation Link

<!-- First focusable element on the page -->
<a href="#main-content" class="skip-link">Skip to main content</a>

<style>
  .skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    padding: 8px 16px;
    background: #1c1917;
    color: #ffffff;
    z-index: 100;
    transition: top 0.2s;
  }
  .skip-link:focus {
    top: 0;
  }
</style>

<main id="main-content" tabindex="-1">
  <!-- Page content -->
</main>

Keyboard Interaction Patterns

Standard keyboard behaviors that users expect:

  • Tab — Move to the next interactive element
  • Shift+Tab — Move to the previous interactive element
  • Enter / Space — Activate buttons and links
  • Escape — Close modals, dropdowns, and popups
  • Arrow keys — Navigate within composite widgets (tabs, menus, listboxes)

Focus Trapping in Modals

function trapFocus(modal) {
  const focusableElements = modal.querySelectorAll(
    'a[href], button:not([disabled]), input:not([disabled]), ' +
    'select:not([disabled]), textarea:not([disabled]), [tabindex="0"]'
  );
  const firstElement = focusableElements[0];
  const lastElement = focusableElements[focusableElements.length - 1];

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

    if (e.shiftKey && document.activeElement === firstElement) {
      e.preventDefault();
      lastElement.focus();
    } else if (!e.shiftKey && document.activeElement === lastElement) {
      e.preventDefault();
      firstElement.focus();
    }
  });

  firstElement.focus();
}

Color and Contrast

Color contrast directly affects readability for everyone, not just users with visual impairments. WCAG defines minimum contrast ratios:

  • Normal text — 4.5:1 minimum (AA), 7:1 enhanced (AAA)
  • Large text (18px+ bold or 24px+ regular) — 3:1 minimum (AA), 4.5:1 enhanced (AAA)
  • UI components and graphics — 3:1 minimum against adjacent colors
/* Checking your color combinations */
/* #1c1917 on #f9f8f6 = contrast ratio ~16:1 (AAA) */
/* #c2724e on #f9f8f6 = contrast ratio ~4.1:1 (check for large text) */
/* #c2724e on #ffffff = contrast ratio ~4.5:1 (meets AA) */

body {
  color: #1c1917;
  background-color: #f9f8f6;
}

a {
  color: #c2724e;
  /* Consider underline for links — don't rely on color alone */
  text-decoration: underline;
}

Never convey information through color alone. If you use a red border to indicate a form error, also add an error icon and a text message. If you use green and red to distinguish success and failure states, add labels or icons that work without color perception.

ARIA: Accessible Rich Internet Applications

ARIA attributes supplement HTML semantics when native elements are insufficient. The first rule of ARIA: do not use ARIA if a native HTML element provides the same functionality. <button> is always better than <div role="button">.

<!-- Tabs pattern with ARIA -->
<div role="tablist" aria-label="Account settings">
  <button role="tab" id="tab-1" aria-selected="true" aria-controls="panel-1">
    Profile
  </button>
  <button role="tab" id="tab-2" aria-selected="false" aria-controls="panel-2">
    Security
  </button>
</div>

<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">
  <!-- Profile content -->
</div>

<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>
  <!-- Security content -->
</div>
<!-- Live regions for dynamic content updates -->
<div aria-live="polite" aria-atomic="true">
  <!-- Screen reader announces changes here -->
  3 items added to cart
</div>

<!-- For urgent announcements -->
<div role="alert">
  Your session will expire in 2 minutes
</div>

Forms and Input Accessibility

Accessible forms require explicit labeling, clear error messaging, and proper grouping of related controls:

<form novalidate>
  <!-- Explicit label association -->
  <div class="field">
    <label for="email">Email address</label>
    <input type="email" id="email" name="email"
           required
           aria-describedby="email-help email-error"
           aria-invalid="false">
    <span id="email-help" class="hint">We will never share your email</span>
    <span id="email-error" class="error" role="alert" hidden></span>
  </div>

  <!-- Grouped radio buttons -->
  <fieldset>
    <legend>Notification preference</legend>
    <label>
      <input type="radio" name="notify" value="email">
      Email notifications
    </label>
    <label>
      <input type="radio" name="notify" value="sms">
      SMS notifications
    </label>
    <label>
      <input type="radio" name="notify" value="none">
      No notifications
    </label>
  </fieldset>

  <button type="submit">Save preferences</button>
</form>
// Client-side validation with accessible error handling
function validateField(input) {
  const errorElement = document.getElementById(`${input.id}-error`);

  if (!input.validity.valid) {
    const message = getErrorMessage(input);
    errorElement.textContent = message;
    errorElement.hidden = false;
    input.setAttribute('aria-invalid', 'true');
  } else {
    errorElement.textContent = '';
    errorElement.hidden = true;
    input.setAttribute('aria-invalid', 'false');
  }
}

WCAG 2.2: What Changed

WCAG 2.2 added nine new success criteria, with several focused on improving the experience for users with cognitive and motor disabilities:

  • Focus Not Obscured (2.4.11, AA) — When an element receives focus, it must not be entirely hidden by other content (sticky headers, cookie banners, chat widgets)
  • Dragging Movements (2.5.7, AA) — Any action that requires dragging must have a single-pointer alternative (click/tap to move instead of drag)
  • Target Size Minimum (2.5.8, AA) — Interactive targets must be at least 24×24 CSS pixels, with exceptions for inline links and targets spaced sufficiently apart
  • Consistent Help (3.2.6, A) — Help mechanisms (contact links, chatbots, help pages) must appear in the same relative location across pages
  • Redundant Entry (3.3.7, A) — Information the user has already entered in a process should be auto-populated or available for selection rather than requiring re-entry

Testing Tools and Workflow

Automated Testing

  • axe DevTools — Browser extension by Deque. Scans the page and reports WCAG violations with remediation guidance. Integrates with CI/CD through axe-core
  • Lighthouse — Built into Chrome DevTools. Includes an accessibility audit alongside performance, SEO, and best practices checks
  • WAVE — Web Accessibility Evaluation Tool by WebAIM. Provides a visual overlay showing errors, alerts, and structural elements
  • Pa11y — Command-line accessibility testing tool that integrates into CI/CD pipelines for automated regression testing
// Automated accessibility testing in CI with axe-core
// cypress/e2e/accessibility.cy.js
describe('Accessibility', () => {
  it('home page has no critical violations', () => {
    cy.visit('/');
    cy.injectAxe();
    cy.checkA11y(null, {
      runOnly: {
        type: 'tag',
        values: ['wcag2a', 'wcag2aa', 'wcag22aa']
      }
    });
  });
});

Manual Testing

  • Keyboard-only navigation — Unplug your mouse and navigate your site using only Tab, Enter, Escape, Space, and arrow keys. Every function should be reachable and usable
  • Screen reader testing — VoiceOver on macOS (built-in), NVDA on Windows (free), JAWS on Windows (commercial). Test the critical user flows — can a screen reader user complete your main tasks?
  • Zoom testing — Zoom to 200% and 400%. Content should reflow without horizontal scrolling at 200%. No content should be lost or overlapping at 400%
  • Color contrast checking — WebAIM Contrast Checker, Stark (Figma plugin), or Chrome DevTools contrast inspection

Frequently Asked Questions

Does accessibility slow down development?

Building accessibility into your workflow from the start adds minimal overhead. Using semantic HTML, labeling form controls, and providing alt text are quick tasks when done habitually. Retrofitting accessibility onto an existing application is expensive and time-consuming. The development cost is in the relearning, not in the practice itself. Teams that build accessibly from day one rarely report meaningful slowdowns.

Is automated testing sufficient for WCAG compliance?

Automated tools catch roughly 30-40% of accessibility issues — mostly structural problems like missing alt text, low contrast, and improper heading hierarchy. The remaining issues require manual testing: keyboard navigation flow, screen reader comprehension, focus management in dynamic interfaces, and the overall usability of the experience. A combination of automated CI checks and periodic manual audits provides the strongest coverage.

What is the legal risk of an inaccessible website?

In the United States, thousands of ADA lawsuits are filed annually against businesses with inaccessible websites. The European Accessibility Act (effective June 2025) requires digital products and services to meet accessibility standards. Many countries have adopted or are adopting similar legislation. Beyond legal compliance, accessibility improves usability for all users, including those with temporary disabilities (broken arm), situational limitations (bright sunlight), and aging-related changes (declining vision).

How do I get started if my site currently has no accessibility features?

Start with the highest-impact changes. Run an axe DevTools audit to identify critical issues. Fix heading hierarchy and landmark structure first — these give screen reader users the ability to navigate your site. Add alt text to all images. Ensure all interactive elements are keyboard accessible and have visible focus indicators. Check color contrast ratios. These foundational fixes address the most common barriers and can often be completed in a few focused sessions.

Accessibility is not an afterthought or a checkbox — it is a fundamental quality attribute of professional web development. Combined with responsive design, performance optimization, and solid framework choices, accessible development produces websites that work for the widest possible audience. The principles that Tim Berners-Lee embedded in the web — universality and access for all — remain as relevant as ever. Your code editor likely has accessibility linting built in. Turn it on.