WCAG 2.1 Accessibility Testing: A Developer's Practical Guide

WCAG 2.1 Accessibility Testing: A Developer's Practical Guide

WCAG 2.1 — the Web Content Accessibility Guidelines, version 2.1 — is the international standard for web accessibility. Published by the W3C in 2018, it defines how to make web content accessible to people with disabilities. For developers, the challenge isn't understanding the principles (they're sensible) — it's translating the abstract success criteria into concrete testing practices.

This guide cuts through the specification language and gives you a practical testing approach: what WCAG requires, which parts automated tools handle, which require manual testing, and the most common violations with their fixes.

The Three Levels of WCAG Compliance

WCAG organizes success criteria into three levels:

Level A — The minimum. Violations at this level create barriers so severe they make a page completely unusable for some users. Think: images with no alt text, form fields with no labels, pages that can't be navigated with a keyboard at all.

Level AA — The standard compliance target. Required by most legislation (ADA, Section 508, EN 301 549, AODA). Adds color contrast requirements, consistent navigation, error identification, and more. When organizations say "we comply with WCAG," they mean WCAG 2.1 AA.

Level AAA — Maximum. Adds requirements that aren't feasible for all content types (sign language interpretation, extended audio description). Rarely required across an entire site; often achieved selectively for specific high-importance pages.

The baseline you should target: WCAG 2.1 AA. This is the legal requirement in most jurisdictions and the right technical target for a professional web application.

The Four Principles (POUR)

Every WCAG success criterion falls under one of four principles:

Perceivable

Content must be presentable in ways users can perceive. Users who can't see must have text alternatives. Users who can't hear must have captions. Content must be separable from its presentation.

Key success criteria:

  • 1.1.1 Non-text content (A) — Images, icons, and controls need text alternatives
  • 1.3.1 Info and relationships (A) — Structure (headings, lists, tables) must be programmatically determinable
  • 1.3.4 Orientation (AA) — Content must not lock to a single screen orientation
  • 1.4.1 Use of color (A) — Color alone cannot convey information
  • 1.4.3 Contrast minimum (AA) — 4.5:1 ratio for normal text, 3:1 for large text
  • 1.4.4 Resize text (AA) — Text must be resizable to 200% without assistive technology

Operable

Interface components must be operable by all users. Everything clickable must also be keyboard-accessible. Users must have enough time. Pages must not cause seizures.

Key success criteria:

  • 2.1.1 Keyboard (A) — All functionality available via keyboard
  • 2.1.2 No keyboard trap (A) — Focus must not be trapped inside a component
  • 2.4.1 Bypass blocks (A) — Skip navigation link required
  • 2.4.2 Page titled (A) — Pages must have descriptive titles
  • 2.4.3 Focus order (A) — Focus order must preserve meaning and operability
  • 2.4.4 Link purpose (A) — Link text describes its destination
  • 2.4.7 Focus visible (AA) — Keyboard focus indicator must be visible
  • 2.5.3 Label in name (A) — Visible label must be in the accessible name

Understandable

Content and operation must be understandable. Language must be identified. Navigation must be consistent. Errors must be identified and explained.

Key success criteria:

  • 3.1.1 Language of page (A)<html lang="..."> must be set
  • 3.2.1 On focus (A) — Focus must not change context unexpectedly
  • 3.3.1 Error identification (A) — Errors must be described in text
  • 3.3.2 Labels or instructions (A) — Labels provided for input
  • 3.3.3 Error suggestion (AA) — Error correction hints provided when possible
  • 3.3.4 Error prevention (AA) — For legal/financial submissions, reversible, checked, or confirmed

Robust

Content must be robust enough to work with current and future assistive technologies. Standard HTML semantics must be used correctly.

Key success criteria:

  • 4.1.1 Parsing (A) — Valid HTML, no duplicate IDs
  • 4.1.2 Name, role, value (A) — All UI components have accessible names and correct roles
  • 4.1.3 Status messages (AA) — Status messages must be programmatically determinable

What Automated Tools Can and Cannot Catch

Automated tools (axe-core, Pa11y, Lighthouse) catch roughly 30–40% of WCAG 2.1 AA violations. This is a useful but incomplete baseline.

Automated tools catch reliably:

Issue Relevant Criterion
Missing alt text 1.1.1
Missing form labels 1.3.1
Color contrast ratios 1.4.3
Missing lang attribute 3.1.1
Missing page title 2.4.2
Invalid ARIA attributes 4.1.2
Duplicate IDs 4.1.1
Incorrect ARIA role hierarchies 4.1.2
<input type="image"> without alt 1.1.1
Empty links and buttons 4.1.2
Missing skip links 2.4.1

Automated tools cannot reliably catch:

Issue Relevant Criterion Why
Meaningful alt text 1.1.1 Tools check presence, not quality
Logical reading order 1.3.2 Context-dependent
Focus order reasonableness 2.4.3 Requires human judgment
Error message quality 3.3.3 Content quality, not structure
Consistent navigation 3.2.3 Requires cross-page awareness
Screen reader announcement quality 4.1.2 Requires actual SR testing
Cognitive load Not in WCAG 2.1 Qualitative

The practical implication: Use automated tools as your baseline and regression gate. Then supplement with manual keyboard testing, screen reader testing, and periodic expert review.

Keyboard Navigation Testing

Keyboard accessibility is one of the most impactful areas to test and one that automated tools largely miss. Here's a systematic manual test:

Basic Keyboard Navigation Checklist

Open your page in Chrome, click somewhere outside the page to ensure the browser isn't focused, then:

  1. Press Tab repeatedly — every interactive element (links, buttons, form fields, custom widgets) should receive a visible focus indicator. Nothing should be skipped.
  2. Check focus order — focus should move logically (usually left-to-right, top-to-bottom following visual layout). After a modal opens, focus should move into the modal. After it closes, focus should return to the trigger.
  3. Press Enter on focused links and buttons — they must activate.
  4. Test dropdowns with arrow keys — native <select> elements work. Custom dropdowns must implement arrow key navigation or they fail 2.1.1.
  5. Press Escape — modals, dropdown menus, and overlays must close.
  6. Look for focus traps — press Tab repeatedly inside a modal. Focus should cycle within the modal, not escape to the page behind it (2.1.2).
  7. Check the skip link — focus the page from the browser address bar, press Tab once. The first focusable element should be a skip link to main content.

Testing Focus Visibility

Focus indicators are required by 2.4.7. Many designs suppress them with:

/* This violates WCAG 2.4.7 — never do this */
*:focus {
  outline: none;
}

The acceptable approach is a custom focus indicator that's still visible:

/* Custom focus style — meets WCAG 2.4.7 */
:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

/* Suppress for mouse users only (modern approach) */
:focus:not(:focus-visible) {
  outline: none;
}

Test by tabbing through the page in Chrome with DevTools open. Verify every interactive element has a visible indicator when focused.

Color Contrast Testing

WCAG 1.4.3 requires:

  • 4.5:1 minimum for normal text (under 18pt / 14pt bold)
  • 3:1 minimum for large text (18pt+ / 14pt+ bold)
  • 3:1 minimum for UI components (button borders, checkbox outlines, icons)

Automated contrast checking

axe-core and Pa11y check contrast for text that exists in the DOM with resolved CSS. They miss:

  • Text in images (check manually)
  • Text that appears only on hover
  • Text with gradients or background images behind it

Manual contrast checking

Use the browser's built-in tools:

Chrome DevTools:

  1. Open DevTools → Elements
  2. Click on a text element
  3. In the Styles panel, click the color swatch
  4. The color picker shows the contrast ratio and whether it passes AA/AAA

Browser extensions:

Fixing contrast:

/* Violation: #767676 on white = 4.48:1 (fails AA) */
color: #767676;

/* Fix: #595959 on white = 7.0:1 (passes AA and AAA) */
color: #595959;

/* Violation: white text on #00a0d1 = 2.85:1 */
color: white;
background: #00a0d1;

/* Fix: white text on #0072a0 = 4.52:1 */
background: #0072a0;

Use Coolors Contrast Checker or the WebAIM Contrast Checker for quick ratio calculations.

Screen Reader Testing Basics

Screen readers announce content in ways that can't be inferred from visual inspection alone. Even with perfect ARIA markup, the actual announcement may be wrong. Basic screen reader testing requires actually using a screen reader.

Setup

macOS — VoiceOver (built-in):

  • Enable: Cmd+F5 or System Settings → Accessibility → VoiceOver
  • Turn off: Cmd+F5
  • Web navigation: VO key (Control+Option) + Arrow keys, Tab for focusable elements

Windows — NVDA (free):

  • Download from nvaccess.org
  • Pairs with Firefox or Chrome
  • Browse mode: Arrow keys read everything; Tab jumps to interactive elements

Windows — JAWS (commercial, industry standard):

  • Common in enterprise and government contexts
  • Free 40-minute trial mode

iOS — VoiceOver:

  • Enable: Settings → Accessibility → VoiceOver
  • Swipe right to move forward, double-tap to activate

What to Test

1. Page title announced on load The page title (from <title>) is the first thing announced when a page loads. It should uniquely identify the current page.

2. Heading navigation VoiceOver: VO+Command+H to navigate headings NVDA: H to navigate headings in browse mode

Verify the heading structure makes the page navigable without visual context.

3. Form field announcements Click or tab to each form field. The screen reader should announce the label, field type, and any error. Test error messages — they should be announced when triggered.

4. Modal dialog behavior When a modal opens, the screen reader should announce the dialog title and move focus inside it. When it closes, focus returns to the trigger. Test this explicitly.

5. Dynamic content (live regions) Content that updates without a page load (notifications, form feedback, search results) needs ARIA live regions to be announced:

<!-- Polite: reads after user pauses -->
<div aria-live="polite" aria-atomic="true" id="status">
  Search returned 42 results
</div>

<!-- Assertive: reads immediately (use sparingly) -->
<div role="alert" aria-live="assertive">
  Error: Your session has expired
</div>

ARIA Landmark Roles

Landmarks let screen reader users jump to major page sections. Every page should have:

Role HTML element Requirement
banner <header> One per page (top-level header)
navigation <nav> All nav elements; label unique ones with aria-label
main <main> One per page
complementary <aside> Sidebar content
contentinfo <footer> One per page (top-level footer)
search <search> or role="search" Search forms

Multiple navs need distinguishing labels:

<nav aria-label="Main">
  <!-- primary navigation -->
</nav>

<nav aria-label="Breadcrumb">
  <!-- breadcrumb trail -->
</nav>

<footer>
  <nav aria-label="Footer">
    <!-- footer links -->
  </nav>
</footer>

NVDA users can press D to navigate between landmarks. Without landmarks, navigating a large page by headings or reading sequentially is their only option.

Practical Testing Checklist

Use this before shipping any new page or significant feature:

Automated (run in CI)

  • axe-core finds no violations (run via Playwright or Cypress)
  • Pa11y CI finds no errors above threshold
  • Lighthouse accessibility score ≥ 90

Manual — Keyboard

  • All interactive elements reachable via Tab
  • Visible focus indicator on every interactive element
  • Modals trap focus correctly
  • Escape closes modals and menus
  • Custom widgets support arrow key navigation
  • Skip link present and functional

Manual — Visual

  • Color contrast verified for all text (not just automated check)
  • Information not conveyed by color alone
  • Error messages visible and descriptive

Manual — Screen Reader (spot check)

  • Page title descriptive
  • Heading hierarchy makes sense when read aloud
  • Form fields announce label + type
  • Error messages announced
  • Dynamic content announced via live regions

Structural

  • <html lang="en"> (or appropriate language)
  • Unique <title> on every page
  • Heading hierarchy starts with h1, no skipped levels
  • All images have appropriate alt text
  • Tables have scope attributes on headers

Common Violations and Fixes

1. Auto-playing media (WCAG 1.4.2)

<!-- Violation -->
<video autoplay>...</video>

<!-- Fix: provide controls, don't autoplay sound -->
<video controls>...</video>
<!-- Or if autoplay required, mute it -->
<video autoplay muted controls>...</video>

2. Timeout warnings not given (WCAG 2.2.1)

If a session expires after inactivity, users must be warned before timeout and given time to extend:

// Warn 2 minutes before timeout
function startTimeoutWarning(sessionDuration, warningBefore = 120000) {
  setTimeout(() => {
    // Show accessible warning dialog with extend/logout options
    showTimeoutWarning();
  }, sessionDuration - warningBefore);
}

3. Focus not managed after route change in SPAs

Single-page apps often don't move focus after navigation, leaving screen reader users stranded at the old content:

// React Router — focus management after navigation
import { useEffect, useRef } from 'react';
import { useLocation } from 'react-router-dom';

function FocusManager() {
  const location = useLocation();
  const mainRef = useRef<HTMLElement>(null);

  useEffect(() => {
    // Move focus to main content after each route change
    mainRef.current?.focus();
  }, [location.pathname]);

  return <main id="main" tabIndex={-1} ref={mainRef}>...</main>;
}

4. aria-label conflicts with visible text (WCAG 2.5.3)

The accessible name must contain the visible label text:

<!-- Violation: aria-label doesn't match visible text -->
<button aria-label="Submit application">Send</button>

<!-- Fix: match the visible text -->
<button aria-label="Send application">Send</button>
<!-- Or better — just use the button text -->
<button>Send application</button>

5. Status messages not announced (WCAG 4.1.3)

<!-- Violation: success message appears visually but isn't announced -->
<div class="success-message" id="save-status">
  Changes saved successfully
</div>

<!-- Fix: aria-live region -->
<div
  class="success-message"
  id="save-status"
  aria-live="polite"
  aria-atomic="true"
>
  Changes saved successfully
</div>

Setting Up an Accessibility Testing Workflow

A practical workflow for a development team:

In development (local):

  • Browser extension (axe DevTools or Accessibility Insights) for quick checks while building
  • jest-axe in component tests for unit-level coverage

In CI (every PR):

  • @axe-core/playwright or axe-cypress in E2E test suite
  • Lighthouse CI for aggregate score tracking
  • Pa11y-CI for URL-based batch scanning of key pages

Periodic manual review (monthly or on major features):

  • Keyboard navigation walkthrough of critical user flows
  • Screen reader test with VoiceOver (macOS) or NVDA (Windows)
  • Color contrast audit of new design system tokens

Before major releases:

  • External accessibility audit by a specialist for anything with legal exposure

The manual testing is where you catch what automation misses: focus order that's technically valid but illogical, error messages that are marked up correctly but confusing, or navigation that works with a mouse but fails the keyboard test because of an unexpected focus order.

Accessibility isn't a checklist you complete once. It's a discipline you practice continuously. Automated testing in CI ensures you don't regress on what's catchable. Manual testing ensures the things that require judgment don't slip through. Both are required for WCAG 2.1 AA compliance — and more importantly, for building software that actually works for the people who depend on these standards.

Read more

Testing Atlantis Terraform PR Automation: Workflows, Plan Verification, and Policy Enforcement

Testing Atlantis Terraform PR Automation: Workflows, Plan Verification, and Policy Enforcement

Atlantis automates Terraform plan and apply through pull requests. But Atlantis itself needs testing: workflow configuration, plan output validation, policy enforcement, and server health checks. This guide covers testing Atlantis workflows locally with atlantis-local, validating plan outputs with custom scripts, enforcing Terraform policies with OPA and Conftest, and monitoring Atlantis

By HelpMeTest