DevToolBoxGRÁTIS
Blog

Checklist de acessibilidade web: Guia WCAG para desenvolvedores

12 minby DevToolBox

Web Accessibility (a11y) Checklist: WCAG 2.2 Compliance Guide

Web accessibility ensures that people with disabilities can use your website effectively. Beyond being a legal requirement in many jurisdictions (ADA, EN 301 549, EAA), accessible sites rank better in search engines, reach more users, and are simply better products. This checklist covers WCAG 2.2 success criteria organized by category, with concrete code examples for each requirement.

The Four Principles of WCAG (POUR)

WCAG 2.2 is organized around four principles. All success criteria fall under one of these:

  • Perceivable — Information must be presentable in ways users can perceive (not invisible to all senses)
  • Operable — UI components and navigation must be operable (keyboard, pointer, or assistive technology)
  • Understandable — Information and UI operation must be understandable
  • Robust — Content must be robust enough to be interpreted by assistive technologies

Criteria are rated at three levels: A (minimum), AA (standard — legally required in most contexts), and AAA (enhanced). Most organizations target WCAG 2.2 Level AA.

1. Perceivable

1.1 Text Alternatives (Level A)

Provide text alternatives for non-text content so it can be changed into other forms (large print, braille, speech, symbols).

<!-- Good: Meaningful alt text describing the image -->
<img src="revenue-chart.png" alt="Q4 2025 revenue: $2.4M, up 34% year over year" />

<!-- Good: Decorative image — empty alt hides from screen readers -->
<img src="decorative-divider.png" alt="" />

<!-- Good: Icon button with accessible label -->
<button aria-label="Close dialog">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

<!-- Bad: Missing alt -->
<img src="product.jpg" />

<!-- Bad: Meaningless alt -->
<img src="chart.png" alt="image" />

1.2 Captions and Transcripts (Level A/AA)

<!-- Video with captions (Level A) -->
<video controls>
  <source src="intro.mp4" type="video/mp4" />
  <track kind="captions" src="captions-en.vtt" srclang="en" label="English" default />
  <track kind="captions" src="captions-es.vtt" srclang="es" label="Español" />
</video>

<!-- Audio-only: provide transcript link (Level A) -->
<audio controls src="podcast.mp3"></audio>
<p><a href="/transcript-ep42.txt">Read transcript for Episode 42</a></p>

1.3 Adaptable Content (Level A)

<!-- Use semantic HTML — don't rely on visual-only presentation for structure -->

<!-- Bad: Visual-only heading -->
<div style="font-size: 24px; font-weight: bold;">My Section</div>

<!-- Good: Semantic heading -->
<h2>My Section</h2>

<!-- Use landmark roles / elements -->
<header role="banner">...</header>
<nav aria-label="Main navigation">...</nav>
<main>...</main>
<aside aria-label="Related articles">...</aside>
<footer>...</footer>

<!-- Correct reading order in DOM (not just visual order via CSS) -->
<!-- Table headers for data tables -->
<table>
  <thead>
    <tr>
      <th scope="col">Name</th>
      <th scope="col">Price</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Widget</td>
      <td>$9.99</td>
    </tr>
  </tbody>
</table>

1.4 Distinguishable (Level A/AA)

/* 1.4.1 Color is not the only way to convey information */
/* Bad: red = error, green = success — only color */
/* Good: color + icon + text label */

/* 1.4.3 Contrast ratio (Level AA) */
/* Normal text: minimum 4.5:1 */
/* Large text (18pt / 14pt bold): minimum 3:1 */
/* Use tools: WebAIM Contrast Checker, browser DevTools */

body {
  color: #1a1a1a;       /* On white: ratio 16.1:1 — passes AAA */
  background: #ffffff;
}

.secondary-text {
  color: #595959;       /* On white: ratio 7:1 — passes AA */
}

/* Avoid this: */
.bad-contrast {
  color: #aaaaaa;       /* On white: ratio 2.3:1 — FAILS */
}

/* 1.4.4 Resize text: use relative units */
body {
  font-size: 1rem;      /* Not px — lets users zoom text independently */
  line-height: 1.5;
}

/* 1.4.10 Reflow (Level AA) — no horizontal scroll at 320px width */
@media (max-width: 320px) {
  .content {
    width: 100%;
    padding: 0 1rem;
  }
}

/* 1.4.11 Non-text contrast (Level AA) */
/* UI components and graphics need 3:1 contrast against adjacent colors */
input {
  border: 2px solid #767676; /* 4.5:1 against white */
}
input:focus {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
}

2. Operable

2.1 Keyboard Accessible (Level A)

<!-- All interactive elements must be reachable via keyboard -->

<!-- Custom button: use <button>, not <div> with click handler -->
<!-- Bad -->
<div onclick="submit()" style="cursor: pointer">Submit</div>

<!-- Good: native button, focusable by default, fires on Enter/Space -->
<button type="button" onclick="submit()">Submit</button>

<!-- If you MUST use a non-semantic element: -->
<div
  role="button"
  tabindex="0"
  onkeydown="handleKey(event)"
  onclick="submit()">
  Submit
</div>

<script>
function handleKey(e) {
  if (e.key === 'Enter' || e.key === ' ') {
    e.preventDefault();
    submit();
  }
}
</script>

<!-- Skip navigation link for keyboard users -->
<a href="#main-content" class="skip-link">Skip to main content</a>
<main id="main-content">...</main>
/* Skip link — visible on focus, hidden otherwise */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #000;
  color: #fff;
  padding: 0.5rem 1rem;
  z-index: 9999;
  text-decoration: none;
}
.skip-link:focus {
  top: 0;
}

2.2 Enough Time (Level A)

// If your site has session timeouts, warn users before expiry
function warnBeforeTimeout(remainingMs) {
  if (remainingMs <= 2 * 60 * 1000) { // 2 minutes remaining
    showTimeoutWarning({
      message: 'Your session expires in 2 minutes.',
      actions: [
        { label: 'Extend session', action: extendSession },
        { label: 'Log out', action: logout },
      ],
    });
  }
}

// For auto-updating content, provide pause controls
// <button aria-pressed="false" id="pause-updates">Pause live updates</button>

2.4 Navigable (Level A/AA)

<!-- 2.4.1 Bypass blocks (Level A): skip links already covered above -->

<!-- 2.4.2 Page titled (Level A) -->
<title>Contact Us | DevToolBox</title>

<!-- 2.4.4 Link purpose (Level A) -->
<!-- Bad: ambiguous link text -->
<a href="/report.pdf">Click here</a>

<!-- Good: descriptive link text -->
<a href="/report.pdf">Download Q4 2025 Annual Report (PDF, 2.1 MB)</a>

<!-- When visual context provides meaning, use aria-label -->
<a href="/blog/redis-caching" aria-label="Read more about Redis caching strategies">
  Read more
</a>

<!-- 2.4.7 Focus visible (Level AA) -->
<!-- Never do this: -->
* { outline: none; }

<!-- Do this instead: style the focus indicator -->
:focus-visible {
  outline: 3px solid #005fcc;
  outline-offset: 2px;
  border-radius: 2px;
}

<!-- 2.4.11 Focus not obscured (WCAG 2.2 Level AA) -->
<!-- Sticky headers must not fully cover focused elements -->
:target {
  scroll-margin-top: 80px; /* Height of sticky header */
}

2.5 Input Modalities (WCAG 2.2 Level A/AA)

/* 2.5.3 Label in name (Level A): visible label text must be in accessible name */
/* If button says "Search", aria-label cannot say "Find" */

/* 2.5.8 Target size (WCAG 2.2 Level AA) */
/* Interactive targets must be at least 24x24 CSS pixels */
button, a, input[type="checkbox"], input[type="radio"] {
  min-width: 24px;
  min-height: 24px;
}

/* Better: 44x44px for comfortable touch targets (WCAG AAA / Apple HIG) */
.touch-target {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

3. Understandable

3.1 Readable (Level A)

<!-- 3.1.1 Language of page (Level A) -->
<html lang="en">

<!-- 3.1.2 Language of parts (Level AA) -->
<p>The French term <span lang="fr">mise en place</span> refers to preparation.</p>

<!-- For multilingual sites -->
<html lang="fr">
  <body>
    <p>Bonjour le monde</p>
    <blockquote lang="en">Hello World</blockquote>
  </body>
</html>

3.2 Predictable (Level A/AA)

<!-- 3.2.2 On input (Level A): don't change context on focus/input without warning -->

<!-- Bad: navigates immediately on select change -->
<select onchange="window.location = this.value">
  <option value="/en">English</option>
  <option value="/fr">Français</option>
</select>

<!-- Good: require explicit submit action -->
<form action="/set-language" method="post">
  <label for="lang">Language:</label>
  <select id="lang" name="lang">
    <option value="en">English</option>
    <option value="fr">Français</option>
  </select>
  <button type="submit">Change language</button>
</form>

3.3 Input Assistance (Level A/AA)

<!-- 3.3.1 Error identification (Level A) -->
<div role="alert" aria-live="assertive">
  <p id="email-error" class="error">
    Please enter a valid email address (example: name@domain.com).
  </p>
</div>

<!-- 3.3.2 Labels or instructions (Level A) -->
<label for="phone">
  Phone number
  <span class="hint">(Format: 555-123-4567)</span>
</label>
<input
  id="phone"
  type="tel"
  pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}"
  aria-describedby="phone-hint phone-error"
  autocomplete="tel"
/>
<p id="phone-hint">Enter your 10-digit US phone number</p>

<!-- 3.3.3 Error suggestion (Level AA) -->
<input
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
/>
<p id="email-error" role="alert">
  Invalid email. Did you mean <button onclick="fixEmail()">user@example.com</button>?
</p>

4. Robust

4.1 Compatible (Level A/AA)

<!-- 4.1.2 Name, role, value (Level A): use ARIA correctly -->

<!-- Native HTML is best — it has built-in ARIA semantics -->
<button>Submit</button>        <!-- role=button, name="Submit" -->
<input type="checkbox" />      <!-- role=checkbox, state: checked/unchecked -->
<a href="/about">About</a>    <!-- role=link, name="About" -->

<!-- Custom components: provide all three -->
<div
  role="switch"
  aria-checked="false"
  aria-label="Enable notifications"
  tabindex="0">
</div>

<!-- 4.1.3 Status messages (Level AA): announce dynamic content -->
<!-- Success message: role="status" (polite) -->
<div role="status" aria-live="polite">
  Your changes have been saved.
</div>

<!-- Error: role="alert" (assertive — interrupts) -->
<div role="alert" aria-live="assertive">
  Error: Failed to save changes. Please try again.
</div>

<!-- Loading state -->
<div role="status" aria-live="polite" aria-label="Loading search results">
  <span aria-hidden="true">Loading...</span>
</div>

ARIA Roles and Landmarks Cheat Sheet

HTML ElementImplicit ARIA RoleUse Case
<header>bannerSite header (once per page)
<nav>navigationNavigation menus
<main>mainPrimary content (once per page)
<aside>complementarySidebar, related content
<footer>contentinfoSite footer (once per page)
<section>region (with label)Thematic sections
<form>formForms with accessible name
<button>buttonActions (not navigation)
<a href>linkNavigation to URLs

Accessibility Testing Tools

  • axe DevTools — Browser extension for automated accessibility testing (catches ~57% of issues)
  • WAVE — Visual feedback overlay for accessibility errors
  • Lighthouse — Built into Chrome DevTools; includes an Accessibility audit
  • NVDA (Windows) / VoiceOver (Mac/iOS) / TalkBack (Android) — Manual screen reader testing
  • Keyboard navigation — Tab through every interactive element manually
  • Color contrast analyzers — WebAIM Contrast Checker, Colour Contrast Analyser desktop app

Quick Wins: The Highest-Impact Fixes

If you are just starting out, these fixes will resolve the most common WCAG violations:

  1. Add descriptive alt text to all images (or alt="" for decorative ones)
  2. Add lang attribute to the <html> element
  3. Associate every form input with a <label> using matching id/for
  4. Never remove :focus styles without replacing them with a visible alternative
  5. Ensure color contrast meets 4.5:1 for normal text and 3:1 for large text
  6. Use semantic heading hierarchy (h1h2h3) — don't skip levels
  7. Add <title> to every page
  8. Make all interactive elements keyboard-operable (use native HTML elements where possible)

For validating your HTML structure and semantic markup, use our HTML Minifier which also highlights structural issues. To check color contrast ratios, try our Color Picker which displays hex, RGB, and HSL values you can verify against WCAG thresholds.

𝕏 Twitterin LinkedIn
Isso foi útil?

Fique atualizado

Receba dicas de dev e novos ferramentas semanalmente.

Sem spam. Cancele a qualquer momento.

Try These Related Tools

🌈CSS Gradient Generator🎨Color Picker Online

Related Articles

CSS :has(): O seletor pai que você sempre quis (2026)

Domine a pseudo-classe CSS :has().

CSS Container Queries: Guia Completo 2026

Domine CSS container queries: sintaxe @container, contenção e unidades de container.

Otimizacao de Performance Web: Guia Core Web Vitals 2026

Guia completo de otimizacao de performance web e Core Web Vitals. Melhore LCP, INP e CLS com tecnicas praticas para imagens, JavaScript, CSS e cache.