Skip to content
Accessibility

WCAG Practical Guide

WCAG 2.2 made practical — understand levels, criteria, and how to meet them in real projects.

stellae.design

11 min read

Introduction

The Web Content Accessibility Guidelines (WCAG) are the international standard for web accessibility. But reading the spec is daunting — dozens of criteria, abstract language, and no code examples. This guide translates WCAG 2.2 into practical, actionable advice for developers.

WCAG has three conformance levels: A (minimum), AA (standard target), and AAA (enhanced). Most organizations target AA, which covers the vast majority of accessibility needs. Each criterion maps to a specific user need — understanding the 'why' makes the 'how' intuitive.

This guide focuses on the criteria most relevant to web applications, with code examples and testing strategies for each.


Key Concepts

The Four Principles (POUR)

  • Perceivable — Information must be presentable in ways all users can perceive (alt text, captions, contrast)
  • Operable — UI must be operable by all users (keyboard, timing, seizures, navigation)
  • Understandable — Content must be understandable (readable, predictable, error help)
  • Robust — Content must be robust enough for diverse user agents (valid HTML, ARIA, compatibility)

Key Level AA Criteria

html
// 1.1.1 Non-text Content — All images need alt text
<img src="chart.png" alt="Revenue increased 23% in Q4 2025" />
<img src="decorative-wave.svg" alt="" /> {/* Decorative: empty alt */}

// 1.4.3 Contrast Minimum — 4.5:1 for normal text, 3:1 for large text
// Use: https://webaim.org/resources/contrastchecker/
.text-body { color: #374151; } /* on white: 10.3:1 ✅ */
.text-muted { color: #9CA3AF; } /* on white: 2.9:1 ❌ */

// 2.1.1 Keyboard — All functionality via keyboard
<button onClick={handleAction}>Action</button> // ✅ Focusable, Enter/Space
<div onClick={handleAction}>Action</div>       // ❌ Not keyboard accessible

// 2.4.7 Focus Visible — Focus indicator must be visible
button:focus-visible {
  outline: 2px solid #3B82F6;
  outline-offset: 2px;
}

Practical Examples

1. Accessible Page Structure

html
<body>
  <a href="#main" className="sr-only focus:not-sr-only">Skip to content</a>
  <header>
    <nav aria-label="Main navigation">
      <ul>{/* nav items */}</ul>
    </nav>
  </header>
  <main id="main">
    <h1>Page Title</h1>
    <section aria-labelledby="section-title">
      <h2 id="section-title">Section</h2>
      <p>Content...</p>
    </section>
  </main>
  <footer>{/* footer */}</footer>
</body>

2. Accessible Form

typescript
<form onSubmit={handleSubmit}>
  <div>
    <label htmlFor="email">Email address</label>
    <input id="email" type="email" aria-describedby="email-help email-error"
      aria-invalid={!!errors.email} required />
    <p id="email-help" className="text-sm text-gray-500">We'll never share your email.</p>
    {errors.email && <p id="email-error" role="alert" className="text-red-500">{errors.email}</p>}
  </div>
  <button type="submit">Subscribe</button>
</form>

3. Live Region for Dynamic Content

typescript
// Announce changes to screen readers
<div aria-live="polite" aria-atomic="true">
  {notification && <p>{notification}</p>}
</div>

// For urgent messages
<div role="alert">
  {error && <p>Error: {error.message}</p>}
</div>

Best Practices

  • ✅ Use semantic HTML first — <button>, <nav>, <main>, <form> provide free accessibility
  • ✅ Test with keyboard only — Tab, Enter, Space, Escape, Arrow keys
  • ✅ Maintain a logical heading hierarchy — h1 → h2 → h3, no skipping
  • ✅ Provide skip links for keyboard users to bypass navigation
  • ✅ Use aria-live regions for dynamic content updates
  • ❌ Don't use div/span with click handlers — use button or anchor
  • ❌ Don't rely on color alone to convey information — add text or icons too

Common Pitfalls

  • Hiding focus outlines with outline: none without providing an alternative
  • Using aria-label on elements that already have visible text — confuses screen readers
  • Auto-playing media without pause controls — violates WCAG 1.4.2
  • Dynamic content changes not announced — use aria-live regions for updates

Accessibilityaccessibility

Was this article helpful?