agentleFS
mrKanohmrKanoh/claude-wcag-accessibility-skill/SKILL.md

wcag-accessibility

Skill9 starsChanged 7 months agoMITView on GitHub

WCAG 2.1/2.2 AA accessibility skill: audits, remediation, ARIA roles and patterns, keyboard navigation, focus management, screen reader behavior (NVDA, JAWS, VoiceOver, TalkBack), color contrast, accessible components (modals, menus, tabs, forms, tables, comboboxes), React, Vue, Angular, Svelte and Next.js, mobile a11y (React Native, SwiftUI, Compose), cognitive accessibility, legal compliance (ADA, EAA, Section 508, EN 301 549, VPAT/ACR) and CI testing (axe-core, pa11y, Playwright). Use when the user asks whether UI is accessible or passes WCAG, asks about ARIA, focus traps, live regions, skip links, contrast ratios or touch targets, or is building or reviewing front-end UI, even without the word "accessibility".

4 other projects use a version of this.

Adopted by

Most recently taken up by

  • Other versions4 projects use a version of this.

    Word for word · 1

    1. mrKanoh/wcag-accessibility-audit-skill★ 9100% same

    Edited · 0

    None found.

    Wrote their own version · 3

    1. dembrandt/dembrandt-skills★ 179same name
    2. LeahyCC/claude-skills★ 3same name
    3. salawi45/skill-wcag-accessibility★ 1same name

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository3 other files it gives its agents.

The file

Show its rules and text

We split this skill into the rules it gives an agent

31 rules. 0 are common, 23 are distinctive, and 8 are very specific to this project.

  • 0

    Common

    Most of the 3 edited versions we read keep these.

  • 23

    Distinctive

    This author's own choices. Worth a look.

  • 8

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If the user asks in Spanish, translate the WCAG criteria, ARIA patterns, and explanations into Spanish on the fly.

    line 22
  2. Very specific:

    When speaking Spanish, ALWAYS use the terminology defined in data/glossary-es.csv to ensure industry-standard translations (e.g., "Screen …

    line 23
  3. Distinctive:

    Never tell the user "the database is in English", just provide the translated answer seamlessly.

    line 24
  4. Very specific:

    Files + execution: if you can read files and run Python, run python scripts/search.py … from the skill root (the directory containing this …

    line 33
  5. Distinctive:

    Language: use person-first ("person with a disability") or identity-first ("Deaf person", "autistic person") as the community prefers.

    line 152
  6. Distinctive:

    Don't use ARIA if a native element exists.

    line 234
  7. Distinctive:

    Don't change native semantics unless you really must.

    line 235
  8. Distinctive:

    All interactive ARIA controls must be keyboard-operable and have an accessible name.

    line 236
  9. Distinctive:

    Never rely on color alone for state (e.g., disabled, selected).

    line 357
  10. Distinctive:

    Always provide a non-drag alternative:

    line 360
  11. Very specific:

    Prefer visible text (see «Before you build a custom component»).

    line 371
  12. Distinctive:

    Keep three things apart when reporting: WCAG requirement (normative text), conformance technique (how to meet it), and design guidance …

    line 423
  13. Very specific:

    Test in dark mode, high contrast mode (forced-colors: active), and color-blindness simulators (deuteranopia, protanopia, tritanopia).

    line 476
  14. Distinctive:

    Never encode meaning in color alone — pair with icon, text, or pattern.

    line 477
  15. Distinctive:

    Ask about assistive tech (AT), customization (zoom level, magnifier, reduced motion) and environment (lighting, bandwidth, noise).

    line 539
  16. Distinctive:

    Component states: default / hover / active / focus / selected / disabled / error

    line 556
  17. Distinctive:

    Contrast-audited tokens: every pair with ratio documented

    line 557
  18. Distinctive:

    Test in Accessibility Inspector (Xcode)

    line 703
  19. Distinctive:

    Test with Accessibility Scanner app

    line 711
  20. Very specific:

    Use <input type="email|tel|number|url|search"> for correct keyboards

    line 715
  21. Distinctive:

    Avoid hover-only affordances (no hover on touch)

    line 717
  22. Very specific:

    Accessibility code review: use LLMs to flag likely issues (missing labels, div-as-button, color-only meaning) as a *supplement*, not a …

    line 727
  23. Distinctive:

    Always respect OS/browser-level accessibility preferences.

    line 1038
  24. Very specific:

    Prefer labels that tell repeated controls apart when their context does not (for example several "Learn more" links).

    line 1158
  25. Distinctive:

    No time limits or extend them (2.2.1)

    line 1189
  26. Distinctive:

    Use icons alongside text for reinforcement

    line 1192
  27. Distinctive:

    No WCAG 3 success criteria with fixed numbers (such as "3.1.1"): requirements in the draft are named (for example "Text contrast sufficient …

    line 1702
  28. Distinctive:

    No contrast algorithm or thresholds: the draft says the contrast algorithm is yet to be determined.

    line 1705
  29. Distinctive:

    If a user asks about APCA, explain that it is a separate contrast model that is not part of WCAG 2.x conformance and not confirmed for WCAG …

    line 1711
  30. Distinctive:

    Language: prefer person-first or identity-first per community norms.

    line 1726
  31. Very specific:

    Cognitive accessibility: when ADHD, dyslexia, memory, or anxiety is mentioned, use section 27 and data/cognitive-accessibility.csv.

    line 1731

“Common” is measured: kept by at least 6 in 10 of the 3 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

WCAG Accessibility Audit Skill

Overview

Comprehensive accessibility knowledge grounded in WCAG 2.1 AA by default (the criteria added in WCAG 2.2 are surfaced where relevant; the searchable catalog lists the Level A and AA criteria of 2.1 plus the 9 added in 2.2, not the 2.1 AAA criteria), WAI-ARIA 1.2, and real-world audit, remediation, and handoff practice. Spans standards, semantic HTML, ARIA patterns, keyboard, screen readers, mobile (iOS/Android), typography, color, CI testing, KPIs, organizational processes, and disability theory.

🌍 Multilingual Behavior (CRITICAL)

You are a polyglot accessibility expert. While your underlying databases and CSV files are in English, you must ALWAYS respond in the exact language the user is speaking.

  • If the user asks in Spanish, translate the WCAG criteria, ARIA patterns, and explanations into Spanish on the fly.
  • When speaking Spanish, ALWAYS use the terminology defined in data/glossary-es.csv to ensure industry-standard translations (e.g., "Screen reader" = "Lector de pantalla", "Landmark" = "Región de la página", "Focus" = "Foco").
  • Never tell the user "the database is in English", just provide the translated answer seamlessly.

References and inspiration: the resources of Paul J. Adam, a web & mobile accessibility specialist (pauljadam.com), are acknowledged as a reference and inspiration. Mentioning him does not imply that he wrote, reviewed or endorses this project. Curriculum themes additionally informed by industry-standard training paths (semantic HTML for designers, ARIA, NVDA, VoiceOver/TalkBack, accessibility audits, inclusive research, accessibility KPIs, handoff, typography, color palette, disability models, mobile, developer test tooling).

Usage modes and limits

These instructions are agent-neutral. How you load them depends on the host:

  1. Native skill (host supports Agent Skills): this file is the core; scripts/ and data/ come from the same installed directory.
  2. Files + execution: if you can read files and run Python, run python scripts/search.py … from the skill root (the directory containing this file) instead of restating tables.
  3. Manual context (no native discovery or no execution): you only know what the user provided. Read the CSVs or sections they supplied; if you cannot run a query, say so, ask for the relevant data or give the exact command for the user to run. Never invent search results, file contents or criteria.

Reading instructions and executing Python are separate capabilities; state which one is missing instead of assuming it.

scripts/search.py needs Python 3.10 or newer; the standard library is enough (no packages to install). The optional MCP server scripts/mcp-server.py (requires the mcp Python package) is part of the public repository but is not included in the Agensi ZIP packages prepared from it.


Searchable Data

CSV databases + a single Python CLI. Run from the skill root:

# WCAG
python scripts/search.py wcag --level AA
python scripts/search.py wcag --keyword contrast
python scripts/search.py wcag --id 2.5.8            # WCAG 2.2 target size
python scripts/search.py wcag --wcag-version 2.1    # records that belong to 2.1 (includes 4.1.1)
python scripts/search.py wcag --wcag-version 2.2 --level AA   # exact AA records that belong to 2.2
# --wcag-version selects what belongs to a version, not only what it added: the 24 AA records are all
# in 2.2, so this equals --level AA. To list the 9 additions use --output json and keep versions == "2.2".

# Legal framework
python scripts/search.py legal --jurisdiction EU
python scripts/search.py legal --jurisdiction Chile

# Glossary ES ↔ EN
python scripts/search.py glossary --keyword lector
python scripts/search.py glossary --context Legal

# ARIA patterns
python scripts/search.py aria --component modal --detail

# Tools
python scripts/search.py tools --type screen-reader --platform Windows --free

# Screen reader shortcuts
python scripts/search.py keys --reader NVDA --action heading

# Semantic HTML reference
python scripts/search.py semantic --element nav

# Typography & color
python scripts/search.py typography --category Spacing
python scripts/search.py color --scope Non-text

# Accessibility KPIs and handoff
python scripts/search.py kpis --category Remediation
python scripts/search.py handoff --phase Design --owner Designer

# Theoretical models of disability
python scripts/search.py models --keyword social

# Resources (books, blogs, research, tools)
python scripts/search.py resources --category Official
python scripts/search.py resources --type Blog
python scripts/search.py resources --authority W3C
python scripts/search.py resources --keyword WCAG
FileContents
data/wcag-criteria.csv59 criteria: all Level A and AA of WCAG 2.1 plus the 9 added in 2.2 (2.4.11–2.4.13, 2.5.7–2.5.8, 3.2.6, 3.3.7–3.3.9); the 28 Level AAA criteria of 2.1 are not included; versions column; 4.1.1 is a historical 2.1 entry removed in 2.2
data/aria-patterns.csv25+ component patterns with required ARIA, keyboard, focus mgmt
data/testing-tools.csv30+ tools by type, platform, browser, cost
data/screen-reader-keys.csvShortcuts for NVDA, JAWS, VoiceOver (macOS/iOS), TalkBack, Narrator
data/semantic-html.csvSemantic HTML reference — element → role → when to use / avoid
data/typography-rules.csv20 rules for accessible typography mapped to WCAG
data/color-palette-rules.csvPalette creation rules (contrast, states, color blindness, HCM)
data/kpis.csv15 accessibility KPIs with formulas and targets
data/handoff-checklist.csvResearch → Design → Dev → QA → Release handoff checklist
data/disability-models.csvMedical / Social / Biopsychosocial / CRPD / etc.
data/glossary-es.csvSpanish ↔ English a11y terminology (70+ terms)
data/legal-framework.csv25+ jurisdictions: laws, standards, effective dates
data/resources.csv50+ authoritative books, blogs, research papers, tools, standards
scripts/search.pyCLI search across all databases
references/form-labels-and-names.mdReviewing form labels, accessible names and input purpose without over-claiming
references/authentication-and-modal-focus.mdWhat 3.3.8 / 3.3.9 require, and how to choose initial and return focus in modal dialogs
references/site-audit-procedure.mdScoped site-audit procedure: inventory, automatic checks, manual protocols, evidence status
templates/audit-report.mdComplete audit report template (fill-in)
templates/site-audit-workbook.mdSite-audit evidence workbook: scope, inventory, criterion matrix, findings
templates/a11y-ci.ymlGitHub Actions CI pipeline (axe + pa11y + Playwright + Lighthouse)
prompts/Ready-to-use prompts for any assistant (audit, ARIA, alt text, handoff, KPIs)

When to Use

  • WCAG 2.0 / 2.1 / 2.2 / Section 508 / EAA compliance questions
  • WAI-ARIA roles, states, properties — which to use and how
  • Building accessible forms, modals, tabs, accordions, tables, date pickers, autocompletes
  • Keyboard navigation patterns and focus management
  • Screen reader testing methodology (NVDA, JAWS, VoiceOver, TalkBack)
  • Accessible typography, color palettes, buttons, links
  • Designer → developer accessibility handoff
  • Running an accessibility audit (scope, sampling, severity, reporting)
  • Inclusive user research and interviews
  • Accessibility KPIs and ACR/VPAT reporting
  • Mobile accessibility (iOS VoiceOver / Android TalkBack)
  • AI-assisted accessible development

1. Disability Models & Inclusive Mindset

Design decisions depend on which model of disability you implicitly assume. Prefer Social and Human Rights (CRPD) models; use Biopsychosocial (WHO ICF) when discussing context and interaction.

ModelViewDesign implication
MedicalImpairment to fixAssistive devices; risk of pathologizing users
SocialBarriers disable people, not impairmentsRemove barriers; universal design
Biopsychosocial (ICF)Interaction of body, activity, participation, environmentConsider context & situational limitations
Human Rights (CRPD)Access is a rightAccessibility is mandatory, not charity
Charity (avoid)Pity-drivenPaternalistic design
Identity / AffirmativeDisability as valued identityCo-design with disabled users

Full table: data/disability-models.csv

Language: use person-first ("person with a disability") or identity-first ("Deaf person", "autistic person") as the community prefers; avoid "suffers from", "wheelchair-bound", "handicapped", "special needs", "normal".

Global statistics (WHO 2023): ~1.3 billion people — 16% of the world's population — live with a significant disability. Plan for permanent, temporary, and situational impairments.


2. WCAG Principles (POUR)

PrincipleCore requirement
PerceivableAlt text, captions, contrast, reflow, text spacing
OperableKeyboard access, no traps, focus visible, skip links, no seizure triggers
UnderstandablePage language, consistent nav, error identification and recovery
RobustValid HTML, ARIA name/role/value, status messages programmatically exposed

High-impact AA criteria (most commonly failed)

IDCriterionRequirement
1.1.1Non-text Contentalt for informative; alt="" for decorative
1.3.1Info and RelationshipsSemantic structure preserved programmatically
1.4.3Contrast (Minimum)4.5:1 text / 3:1 large (18pt or 14pt bold)
1.4.10ReflowUsable at 320 CSS px with no horizontal scroll
1.4.11Non-text Contrast3:1 vs adjacent colors for visual info needed to identify UI components and states (e.g. an author-styled focus indicator) and graphics needed to understand content; exceptions: inactive components, user-agent default appearance not modified by the author, essential graphics
1.4.12Text SpacingSupports line-height 1.5, paragraph 2×, letter 0.12×, word 0.16×
2.1.1KeyboardAll functionality operable by keyboard
2.1.2No Keyboard TrapFocus must never get stuck
2.4.3Focus OrderLogical focus sequence
2.4.7Focus VisibleA keyboard focus indicator must be visible (sets no contrast ratio; see 1.4.11 / 2.4.13)
3.3.1Error IdentificationErrors described in text
4.1.2Name, Role, ValueInteractive elements expose name/role/state
4.1.3Status MessagesDynamic updates announced without focus move

WCAG 2.2 additions (new since 2.1)

IDLevelCriterionWhat changed
2.4.11AAFocus Not Obscured (Minimum)Focused element can't be entirely hidden by author-created content such as sticky UI (no contrast requirement)
2.4.12AAAFocus Not Obscured (Enhanced)…or partially hidden
2.4.13AAAFocus AppearanceIndicator area ≥ a 2 CSS px perimeter of the unfocused component, and 3:1 between the same pixels focused vs unfocused; exceptions: user-agent indicator, or indicator and its background not modified by the author
2.5.7AADragging MovementsDrag operations need a single-pointer alternative
2.5.8AATarget Size (Minimum)Pointer targets ≥ 24×24 CSS px (exceptions apply)
3.2.6AConsistent HelpHelp mechanisms in consistent relative order
3.3.7ARedundant EntryDon't force re-entry of already-provided info
3.3.8AAAccessible Authentication (Minimum)No step of an authentication process requires a cognitive function test unless that step offers an alternative method, an assisting mechanism (e.g. password manager, paste), object recognition, or identifying the user's own non-text content
3.3.9AAAAccessible Authentication (Enhanced)Same as 3.3.8, but only the alternative and the assisting mechanism remain as exceptions (object recognition and personal content are not)

Criteria catalog (59 rows, not complete: no 2.1 AAA criteria; versions shows 2.1 / 2.2; --wcag-version 2.1|2.2 filters by it, and --level AA means exactly AA, not A+AA): data/wcag-criteria.csv


3. Semantic HTML First

Rule of thumb: semantic HTML > ARIA. Only reach for ARIA when no native element provides the semantics.

<header>       <!-- role=banner at top-level -->
<nav aria-label="Primary">
<main>         <!-- exactly one per page -->
<aside>        <!-- role=complementary -->
<footer>       <!-- role=contentinfo at top-level -->
<section aria-labelledby="h">  <!-- region if labeled -->
<article>
<h1>…<h6>      <!-- never skip levels; one h1 per page -->
<button type="button">   <!-- actions -->
<a href="/x">            <!-- navigation -->
<ul><li>        <dl><dt><dd>
<table><caption><thead><th scope="col">
<label for="id">  <fieldset><legend>
<details><summary>        <!-- native disclosure -->
<dialog>                  <!-- showModal(): page behind becomes inert; check Tab and Escape in your target browsers -->

Common mistake: using <div onclick> for a button — breaks keyboard, SR, and focus. Always prefer <button>.

Full element reference: data/semantic-html.csv


4. WAI-ARIA Quick Reference

Three rules of ARIA

  1. Don't use ARIA if a native element exists.
  2. Don't change native semantics unless you really must.
  3. All interactive ARIA controls must be keyboard-operable and have an accessible name.

Essential roles

role="dialog"      aria-modal="true"
role="alertdialog"
role="alert"                             <!-- assertive live region -->
role="status"                            <!-- polite live region -->
role="tablist" role="tab" role="tabpanel"
role="combobox" role="listbox" role="option"
role="grid" role="row" role="gridcell"
role="progressbar" role="slider" role="switch"
role="tree" role="treeitem"
role="menu" role="menuitem"

role="menu" is for application menus; for site navigation use nav + a list of links, with a disclosure button (aria-expanded, aria-controls) for submenus (APG Disclosure Navigation: the menu role implies complex, application-style keyboard behaviour that assistive technology expects and that site navigation neither provides nor needs). A <select> that navigates as soon as its value changes causes a change of context that fails 3.2.2 unless the user is told beforehand (G13); a «Go» submit button (G80) is the robust option. See Understanding 3.2.2.

Essential properties & states

aria-label="Close"
aria-labelledby="heading-id"
aria-describedby="hint-id"
aria-expanded="true|false"
aria-selected="true|false"
aria-checked="true|false|mixed"
aria-hidden="true"                 <!-- never on focusable elements -->
aria-live="polite|assertive"
aria-invalid="true"
aria-required="true"
aria-modal="true"
aria-disabled="true"
aria-sort="ascending|descending"
aria-current="page|step|date|true"
aria-haspopup="menu|listbox|dialog|grid|tree"
aria-activedescendant="id"
aria-controls="id"
aria-owns="id"

Full component patterns: data/aria-patterns.csv


5. Accessible Components

Before you build a custom component

Design guidance, not WCAG requirements (source: Heydon Pickering, Inclusive Components, 2018–2019; the chapter is cited per item). Ask first whether a simpler, native pattern does the job:

  • Tooltip (book chapter «Tooltips & Toggletips»): last resort. Show the information as visible text instead. If you need one, use the recipe in «Tooltip and toggletip (last resort)» below.
  • Tabs («Tabbed Interfaces»): avoid when there are more than about four, or when nothing needs them; use an accordion (disclosure buttons) or plain headed sections. The APG tabs pattern gives the tabpanel tabindex="0" when its first content is not focusable; the book documents a variant of panel focus, and this skill follows the APG.
  • Modal dialog («Modal Dialogs»): only for something urgent or critical. Consider confirm() (the book) or native <dialog> (this skill, see below) first, or inline content.
  • Carousel («Content Slider»): prefer a scrollable or stepped slider that never moves by itself. A scrolling slider must be keyboard-reachable (a named region with tabindex="0" if it overflows). WCAG 2.2.2 applies to content that starts automatically, moves for more than 5 seconds and appears alongside other content: it needs pause, stop or hide.
  • Menu («Menus & Menu Buttons»): role="menu" is for application actions, not site navigation; use nav with a list of links (see §4).

Modal Dialog

<div role="dialog" aria-modal="true" aria-labelledby="title" aria-describedby="desc">
  <h2 id="title">Confirm Delete</h2>
  <p id="desc">This action cannot be undone.</p>
  <button>Delete</button>
  <button aria-label="Close dialog">×</button>
</div>
  • Initial focus depends on the content and the task (APG guidance; WCAG does not name the element): the first focusable element for a simple dialog; a static title or the start of long or structured content (tabindex="-1", focused by script, not a Tab stop) so reading starts at the top; the least destructive action when the action is hard to reverse
  • On close, focus normally returns to the trigger; if it no longer exists or the flow moves on, to a logical element of the workflow
  • Keyboard (APG): Tab and Shift+Tab stay inside the dialog and Escape closes it. WCAG asks that keyboard users are not trapped (2.1.2) and can operate the dialog (2.1.1)
  • Background: inert (or aria-hidden="true" with care)
  • role="dialog" and aria-modal="true" only describe the dialog; an inert background, Tab containment, Escape and focus management must be implemented. Native <dialog>.showModal() makes the rest of the document inert, applies the HTML dialog focusing steps and restores focus to the previously focused element on close; an inert background is not the same as a Tab cycle written in JavaScript, so verify Tab, Escape and initial focus in your target browsers. Details: references/authentication-and-modal-focus.md

Tab Panel

<div role="tablist" aria-label="Account settings">
  <button role="tab" id="t1" aria-selected="true"  aria-controls="p1">Profile</button>
  <button role="tab" id="t2" aria-selected="false" aria-controls="p2" tabindex="-1">Security</button>
</div>
<div role="tabpanel" id="p1" aria-labelledby="t1">…</div>
<div role="tabpanel" id="p2" aria-labelledby="t2" hidden>…</div>
  • Arrow keys switch tabs; Home/End jump to first/last
  • Active tab tabindex="0"; inactive tabindex="-1" (roving tabindex)

Accessible Form Validation

<label for="email">Email <span aria-hidden="true">*</span></label>
<input id="email" type="email"
       autocomplete="email"
       aria-required="true"
       aria-invalid="true"
       aria-describedby="email-help email-error">
<span id="email-help">We'll never share it.</span>
<span id="email-error" role="alert">Enter a valid email address.</span>

Combobox (Autocomplete)

<label for="search">Search</label>
<input id="search" type="text" role="combobox"
       aria-expanded="true" aria-haspopup="listbox"
       aria-controls="results" aria-autocomplete="list"
       aria-activedescendant="opt-2">
<ul id="results" role="listbox">
  <li id="opt-1" role="option">Apple</li>
  <li id="opt-2" role="option" aria-selected="true">Avocado</li>
</ul>

Accessible Button vs. Link (WCAG 2.2 friendly)

<!-- Action → button. Toggle: fixed label + aria-pressed -->
<button type="button" aria-pressed="false">Like</button>

<!-- Navigation → link -->
<a href="/dashboard">Dashboard</a>

<!-- Icon button (always needs accessible name) -->
<button aria-label="Delete item">
  <svg aria-hidden="true" focusable="false">…</svg>
</button>
  • Toggle buttons use ONE form: a fixed label with aria-pressed, or a label that changes with no aria-pressed. Don't change the label and the state at the same time (APG button pattern).
  • Targets ≥ 24×24 CSS px (WCAG 2.5.8). 44×44 recommended for touch.
  • Visible focus ring (WCAG 2.4.7); if you style it, ≥ 3:1 vs adjacent colors (WCAG 1.4.11). 2.4.13 (AAA) adds size and focused-vs-unfocused contrast.
  • Never rely on color alone for state (e.g., disabled, selected).

Accessible Drag & Drop (WCAG 2.5.7)

Always provide a non-drag alternative:

<ul role="listbox" aria-label="Reorder items">
  <li>Item A
    <button aria-label="Move up">↑</button>
    <button aria-label="Move down">↓</button>
  </li>
</ul>

Tooltip and toggletip (last resort)

Prefer visible text (see «Before you build a custom component»). The title attribute is not a substitute: it is not keyboard- or touch-accessible, and browser-controlled tooltips fall under the 1.4.13 exception, so they are not covered by its protections.

Tooltip = supplementary description of a focusable control. The control keeps its own accessible name; the tip is its description (aria-describedby). Use aria-labelledby only when the tip is the control's name (icon-only control without other text). Requirements (WCAG 1.4.13, checked 2026-11-06): dismissible without moving pointer or focus (Esc), hoverable (the pointer can move onto the tip), persistent (stays until hover/focus leaves or the user dismisses). Appear on focus and hover; no interactive content inside. The APG tooltip pattern is marked work in progress (no task-force consensus yet), so treat it as provisional.

<span class="tip-wrap">
  <button type="button" aria-describedby="tip-save">Save</button>
  <span role="tooltip" id="tip-save" hidden>Saves a draft. Nothing is published.</span>
</span>
<script>
document.querySelectorAll('.tip-wrap').forEach((wrap) => {
  const tip = wrap.querySelector('[role="tooltip"]');
  let hovered = false, focused = false;
  const update = () => { tip.hidden = !(hovered || focused); };
  wrap.addEventListener('mouseenter', () => { hovered = true; update(); });
  wrap.addEventListener('mouseleave', () => { hovered = false; update(); });
  wrap.addEventListener('focusin',    () => { focused = true; update(); });
  wrap.addEventListener('focusout',   () => { focused = false; update(); });
  document.addEventListener('keydown', (e) => {   // Esc hides it; focus does not move
    if (e.key === 'Escape' && !tip.hidden) { hovered = focused = false; tip.hidden = true; }
  });
});
</script>

Position the tip touching or overlapping the trigger (no gap), so the pointer can reach it without leaving .tip-wrap. If the control sits inside a dialog, Esc can close both the tip and the dialog; closing only the tip requires listening in the capture phase (addEventListener('keydown', fn, true)) and calling stopPropagation() only while the tip is visible.

Toggletip = a button that reveals information that is not the control's label, announced through a live region. The role="status" container exists in the DOM before the content is inserted. Closes with Esc, when focus leaves, or on a click outside (some browsers, e.g. Safari on macOS, do not focus a button on click, so no blur fires).

<span class="toggletip">
  <button type="button" aria-label="More about password rules">i</button>
  <span role="status"></span>
</span>
<script>
document.querySelectorAll('.toggletip').forEach((box) => {
  const btn = box.querySelector('button');
  const live = box.querySelector('[role="status"]');
  btn.addEventListener('click', () => {
    live.textContent = '';  // re-insert so a repeat click is announced again
    setTimeout(() => { live.textContent = 'Use at least 12 characters.'; }, 100);
  });
  const close = () => { live.textContent = ''; };
  btn.addEventListener('blur', close);
  box.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
  document.addEventListener('click', (e) => { if (!box.contains(e.target)) close(); });
});
</script>

Pointer persistence also applies to a toggletip if it opens on hover; opening on click avoids that. Screen reader announcement of live regions varies; test with your target pairs.


6. Accessible Typography

Keep three things apart when reporting: WCAG requirement (normative text), conformance technique (how to meet it), and design guidance (useful, but not a pass/fail test). Do not fail content for missing a design guideline.

TopicTypeGuidanceSource
Resize textRequirement (AA)Text resizes up to 200% without assistive technology and without loss of content or functionality; exceptions: captions, images of text. Browser zoom or text-only resize may satisfy it; do not block it1.4.4
Body size (16 px / 1rem)Design guidanceA common starting size, not a WCAG minimum. Smaller text is not a 1.4.4 failure by itself; rem does not guarantee accessibilityUnderstanding 1.4.4
Text spacingRequirement (AA)No loss of content or functionality when the user sets all four together: line height 1.5, spacing after paragraphs 2, letter 0.12, word 0.16 (× font size). Content need not start with these values and need not offer a spacing control. Languages and scripts that lack a property may conform with the properties they use1.4.12
Line height / paragraph spacing as initial designDesign guidanceAbout 1.5 line height is a common start. A smaller initial value is not a 1.4.12 failure; content or functionality lost under the overrides is. Truncation with an ellipsis can still conform if the full content stays available through an accessible mechanism (e.g. revealed on focus or activation, or shown on a linked page); truncation with no such mechanism fails. Hover, title or aria-label alone are not a universal solution or sufficient proofUnderstanding 1.4.12
Line lengthDesign guidance; AAA45–75 characters is a common readability target, not WCAG. AAA 1.4.8 requires a mechanism (page or user agent) for width ≤ 80 characters (40 CJK)1.4.8
AlignmentDesign guidance; AAAAvoid fully justified blocks; align to the start edge of the writing direction (left in LTR, right in RTL). AAA 1.4.8 requires a mechanism so text is not justified, not a particular default look1.4.8
ReflowRequirement (AA)Usable at 320 CSS px width1.4.10
ContrastRequirement (AA)4.5:1 body / 3:1 large (18pt, or 14pt bold)1.4.3
Font weightDesign guidancePrefer regular or heavier weights for body text. 1.4.3 sets no minimum weight. Size and bold together classify text as large (≥ 18 pt, or ≥ 14 pt bold; equivalent sizes for other fonts), so AA requires 4.5:1 for normal text and 3:1 for large text, subject to the criterion's exceptions. Very thin strokes may benefit from heavier strokes or more contrast: a legibility recommendation, not a mandatory weight or an automatic failureUnderstanding 1.4.3
TypefaceDesign guidanceNo WCAG criterion requires a specific face, and no font guarantees benefits for readers with dyslexia. Choose clearly distinguishable letterforms and test with your audience—
Avoid all-caps for long copyDesign guidanceCan reduce legibility; screen readers may spell some words—
No text-in-imagesRequirement (AA)Use real text (logos exempt)1.4.5
/* Manual test aid for 1.4.12, NOT production styles and NOT a complete check.
   Apply all four together (and change nothing else), then look for clipped,
   overlapping, truncated or hidden text and lost functionality. Truncated text is a
   signal to review (is the full content still available?), not an automatic verdict. */
* {
  line-height: 1.5 !important;
  letter-spacing: 0.12em !important;
  word-spacing: 0.16em !important;
}
p { margin-bottom: 2em !important; }

Audit cases: text under 16 px, a weight under 400, or an initial line height under 1.5 are not failures by themselves — report a barrier only with evidence (e.g. content lost at 200% or under the 1.4.12 overrides). Under an AA target, do not apply the AAA conditions of 1.4.8 automatically.

Full ruleset: data/typography-rules.csv


7. Accessible Color Palettes

Contrast matrix (WCAG 2.1 / 2.2)

ContextRatio
Body text (AA)4.5:1
Large text (AA) — 18pt / 14pt bold3:1
Body text (AAA)7:1
UI components / icons / borders3:1 (1.4.11)
Author-styled focus indicator vs adjacent colors3:1 (1.4.11; 2.4.7 requires it to be visible)
Focus indicator, same pixels focused vs unfocused (AAA)3:1 plus size rule (2.4.13)

Palette design workflow

  1. Define semantic tokens: text, text-muted, bg, surface, border, primary, success, warning, error, focus.
  2. For each token, define default / hover / active / focus / disabled variants.
  3. Audit every pair used in UI against the matrix.
  4. Test in dark mode, high contrast mode (forced-colors: active), and color-blindness simulators (deuteranopia, protanopia, tritanopia).
  5. Never encode meaning in color alone — pair with icon, text, or pattern.
/* Windows High Contrast Mode support */
@media (forced-colors: active) {
  button { border: 1px solid ButtonText; }
  :focus-visible { outline: 2px solid CanvasText; }
}

Full rules: data/color-palette-rules.csv


8. Keyboard Interaction Patterns

WidgetKeys
LinkTab, Enter
ButtonTab, Enter, Space
Checkbox / SwitchTab, Space
Radio groupTab onto group; Arrow keys between
Select / ListboxTab, Arrows, Home/End, type-ahead
ComboboxTab, Arrows, Enter, Esc
TabsTab to list; Arrow keys switch; Enter/Space activate
Menu / Menu bar (application menus only; not site navigation, see §4)Arrow keys, Enter, Esc, Tab exits
DialogTab cycles inside; Esc closes
TreeArrows (→ expand, ← collapse), Enter activates
Grid / Data tableArrow keys between cells; Enter activates cell editor
SliderArrow keys, Home/End, PgUp/PgDn
Date pickerArrow keys calendar, PgUp/PgDn months

9. Audit Methodology

For a site audit, read the audit execution procedure and fill the audit workbook. They define scope, page/state inventory, manual protocols and evidence status. Include the optional institutional inclusion review only when requested. These resources guide an auditor; they do not add a crawler, run axe or operate a screen reader.

Structured audit framework (matches professional curriculum):

1. Scope       Sample pages (home, template types, auth, checkout, top 10 by traffic)
2. Standard    WCAG 2.1/2.2 level (usually AA); jurisdictional overlays (Section 508, EN 301 549, EAA)
3. Automated   axe DevTools + Lighthouse + WAVE → baseline violations
4. Keyboard    Tab through every flow; verify reachability + visible focus
5. Screen AT   NVDA+Firefox, VoiceOver+Safari, TalkBack+Chrome
6. Visual      Zoom 200% & 400%; reflow 320 px; text-spacing override
7. Manual SC   Check non-automatable criteria (1.3.1 structure, 2.4.3 order, 3.3.x errors, 2.5.x target size, 2.4.11 focus obscured)
8. Findings    One row per issue: URL · element · criterion · severity · steps · recommendation
9. Severity    Critical / Serious / Moderate / Minor (align to Deque or internal scale)
10. Report     Executive summary, remediation plan, VPAT/ACR update, retest plan

Sampling guidance: choose representative templates rather than every page. Re-audit the same sample after remediation to measure regression.

ACR/VPAT: maintain a current Accessibility Conformance Report mapping every WCAG SC → Supports / Partially Supports / Does Not Support / Not Applicable with rationale.


10. Inclusive Research & Interviews

  • Recruit across disability categories: vision (blind, low-vision, color-blind), hearing (Deaf, HoH), motor (switch, voice, limited dexterity), cognitive (dyslexia, ADHD, memory), speech, seizure sensitivity.
  • Ask about assistive tech (AT), customization (zoom level, magnifier, reduced motion) and environment (lighting, bandwidth, noise).
  • Provide accessible recruitment materials (captioned videos, plain language, screen-reader-friendly forms).
  • Offer choice of modality (video with captions, audio only, async written, in-person).
  • Compensate equitably; avoid extractive research.
  • Reflect findings back to participants; credit collaboration.

Consent: explicit recorded consent; right to pause/withdraw without penalty.


11. Designer → Developer Handoff

Handoff artifacts a designer must include:

  • Semantic structure: headings, landmarks, lists marked in the design
  • Focus order: numbered arrows through interactive elements
  • Keyboard spec: which keys do what for each custom component
  • ARIA annotations: role, accessible name, states
  • Component states: default / hover / active / focus / selected / disabled / error
  • Contrast-audited tokens: every pair with ratio documented
  • Motion: prefers-reduced-motion alternative
  • Target size: ≥ 24×24 CSS px (ideally 44×44 for touch)
  • Errors & status: inline + live region pattern

Full phase-by-phase checklist: data/handoff-checklist.csv (Research → Design → Dev → QA → Release).


12. Accessibility KPIs

Measure what you ship, not only what you scan.

KPITargetCadence
Axe violations per 1k DOM nodes< 1.0Per build
WCAG AA conformance rate (pages)100%Quarterly
Critical issues open0Weekly
MTTR (mean time to remediate)< 14 daysMonthly
Keyboard task completion rate≥ 95%Per release
Screen reader task success rate≥ 90%Per release
Focus-visible coverage100%Per build
Alt text presence on informative images100%Per build
VPAT/ACR freshness≤ 180 daysQuarterly
Accessibility AC coverage in user stories≥ 90%Sprint

Full KPI catalog: data/kpis.csv


13. Testing: Automation + Manual

Automated — recipes

# axe-core CLI
npx @axe-core/cli https://example.com --tags wcag2a,wcag2aa,wcag21a,wcag21aa,wcag22aa

# pa11y (wraps axe + HTML_CodeSniffer)
npx pa11y https://example.com --standard WCAG2AA

# Lighthouse
npx lighthouse https://example.com --only-categories=accessibility

Jest + Testing Library + jest-axe

import { render } from '@testing-library/react';
import { axe, toHaveNoViolations } from 'jest-axe';
expect.extend(toHaveNoViolations);

test('modal has no a11y violations', async () => {
  const { container } = render(<MyModal open />);
  expect(await axe(container)).toHaveNoViolations();
});

Playwright + axe

import { test } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test('homepage has no axe violations for the selected rules', async ({ page }) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
    .analyze();
  expect(results.violations).toEqual([]);
});

Cypress + cypress-axe

cy.visit('/');
cy.injectAxe();
cy.checkA11y(null, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'] } });

Storybook a11y addon

// .storybook/main.js
export default { addons: ['@storybook/addon-a11y'] };

GitHub Actions

- name: a11y tests
  run: |
    npm ci
    npm test -- --testPathPattern=a11y
    npx playwright test --grep @a11y

Reminder: automated scans catch ~20–40% of issues. Always pair with keyboard and real AT.


14. Screen Reader Testing

ReaderOSPair withCost
NVDAWindowsFirefox or ChromeFree
JAWSWindowsChrome or EdgePaid
NarratorWindowsEdgeBuilt-in
VoiceOvermacOS / iOSSafariBuilt-in
TalkBackAndroidChromeBuilt-in

NVDA quickstart (Windows)

  • Insert is the NVDA modifier key (or CapsLock in laptop layout).
  • Ctrl silences speech.
  • H / Shift+H next/previous heading.
  • F / B / L / T / K next form field / button / list / table / link.
  • Insert+F7 elements list (headings, links, form fields).
  • Insert+Space toggle browse / focus mode.

VoiceOver (macOS) — VO = Control+Option

  • VO+Right/Left next/prev element
  • VO+U rotor (headings, links, form controls…)
  • VO+Space activate
  • VO+Command+H next heading
  • VO+Shift+M context menu

VoiceOver (iOS)

  • Swipe right/left → navigate; double-tap → activate
  • Three-finger swipe → scroll
  • Two-finger rotation → rotor
  • Three-finger triple-tap → screen curtain

TalkBack (Android)

  • Swipe right/left → navigate
  • Double-tap → activate
  • Two-finger swipe → scroll
  • Swipe up then right → global context menu

Full shortcut catalog: data/screen-reader-keys.csv


15. Mobile Accessibility

iOS (VoiceOver)

  • Touch targets ≥ 44×44 pt
  • accessibilityLabel, accessibilityHint, accessibilityTraits set correctly
  • Custom gestures must have single-tap alternatives
  • Dynamic Type: respect user font size (UIFontMetrics)
  • Reduce Motion: honor UIAccessibility.isReduceMotionEnabled
  • Test in Accessibility Inspector (Xcode)

Android (TalkBack)

  • Touch targets ≥ 48×48 dp
  • contentDescription on non-text elements
  • android:labelFor to link labels to inputs
  • importantForAccessibility="no" on decorative views
  • Support large font scale and high-contrast text
  • Test with Accessibility Scanner app

Web on mobile

  • Respect prefers-reduced-motion, prefers-contrast, prefers-color-scheme
  • Use <input type="email|tel|number|url|search"> for correct keyboards
  • autocomplete tokens (email, name, street-address, postal-code, cc-number…)
  • Avoid hover-only affordances (no hover on touch)

16. AI-Assisted Accessible Development

  • Generate alt text drafts with vision models; review & edit — never ship unreviewed.
  • Explain ARIA patterns but verify against the APG — LLMs can hallucinate roles/states.
  • Translate WCAG to user stories: convert each failing SC to acceptance criteria.
  • Summarize audit findings into exec-friendly language while preserving SC IDs.
  • Accessibility code review: use LLMs to flag likely issues (missing labels, div-as-button, color-only meaning) as a supplement, not a replacement, for real testing.
  • Caution: LLMs often suggest ARIA where semantic HTML would be correct. Apply Rule #1 of ARIA.

17. Common Issues & Fixes

IssueFix
Missing alt textDescriptive alt for informative; alt="" for decorative
Poor color contrast≥ 4.5:1 body, 3:1 large (1.4.3)
No keyboard support<button>; or tabindex="0" + Enter/Space handlers
Invisible focus:focus-visible { outline: 2px solid; }; never outline: none
Missing form labels<label for="id"> or aria-label / aria-labelledby
Bad heading hierarchyh1→h2→h3 in order
ARIA misuseSemantic HTML first
Focus trapped in modalTab wraps inside; Escape closes; focus returns to the trigger (or a logical element if it is gone)
Unlabeled icon buttonaria-label on <button> + aria-hidden="true" on SVG
Auto-playing mediaPause/stop control within 3s
AJAX content not announcedaria-live="polite" region; inject content into it
Color as sole conveyorAdd text, icon, or pattern
Sticky header hides focus (2.4.11)scroll-padding-top: <header-height>
Small tap targets (2.5.8)≥ 24×24 CSS px; spacing can satisfy
Drag-only reorder (2.5.7)Provide ↑/↓ buttons or menu
Password or code paste/autofill blocked (3.3.8, F109)Allow paste; allow password managers and autofill

18. Stack-Specific Code Examples

Next.js App Router — Focus management on navigation

Next.js already ships a route announcer for client-side transitions (next/link): it reads document.title, then the <h1>, then the pathname (https://nextjs.org/docs/architecture/accessibility, checked 2026-11-06). Do not add a second live region for the same announcement. Give every page a unique, descriptive title; that is what gets announced. If you also want focus to move to the new page's heading (a design choice, not a WCAG requirement), use a small client component and test with a screen reader that title announcement plus the heading read-out is not too verbose:

// app/route-focus.tsx — render <RouteFocus /> once inside <body> in app/layout.tsx
'use client';
import { usePathname } from 'next/navigation';
import { useEffect, useRef } from 'react';

export function RouteFocus() {
  const pathname = usePathname();
  const prevPathname = useRef(pathname);

  useEffect(() => {
    // Only act when the path actually changed; this also survives React Strict Mode,
    // which runs effects twice in development and would defeat a "first render" flag.
    if (prevPathname.current === pathname) return;
    prevPathname.current = pathname;
    const h1 = document.querySelector<HTMLElement>('main h1');
    if (h1) {
      h1.setAttribute('tabindex', '-1'); // focusable by script, not in the tab order
      h1.focus();
    }
  }, [pathname]);

  return null;
}

The component assumes each page renders a <main id="main-content"> containing one <h1>, with the skip link before it; app/layout.tsx stays a Server Component so it can still export metadata:

// app/layout.tsx (excerpt)
<body>
  <a href="#main-content" className="skip-link">Skip to main content</a>
  <RouteFocus />
  <main id="main-content">{children}</main> {/* each page renders its own <h1> */}
</body>

Angular — Accessible reactive form

// login.component.ts
import { Component } from '@angular/core';
import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()" novalidate>
      <div>
        <label for="email">Email <span aria-hidden="true">*</span></label>
        <input
          id="email"
          type="email"
          formControlName="email"
          autocomplete="email"
          [attr.aria-invalid]="email.invalid && email.touched"
          [attr.aria-describedby]="email.invalid && email.touched ? 'email-error' : null"
          aria-required="true"
        />
        <span
          id="email-error"
          role="alert"
          *ngIf="email.invalid && email.touched"
        >
          {{ email.hasError('required') ? 'Email is required' : 'Enter a valid email' }}
        </span>
      </div>
      <button type="submit" [disabled]="form.invalid">Log in</button>
    </form>
  `
})
export class LoginComponent {
  form = this.fb.group({ email: ['', [Validators.required, Validators.email]] });
  get email() { return this.form.controls.email; }
  constructor(private fb: FormBuilder) {}
  submit() { /* … */ }
}

Svelte 5 — Modal dialog with native <dialog>

<script>
  import { onMount } from 'svelte';
  let { open = false, onClose } = $props();
  let dialogEl;

  $effect(() => {
    if (open) dialogEl?.showModal();
    else dialogEl?.close();
  });
</script>

<dialog
  bind:this={dialogEl}
  aria-labelledby="dialog-title"
  aria-describedby="dialog-desc"
  on:close={onClose}
  on:click|self={onClose}
>
  <h2 id="dialog-title">Confirm action</h2>
  <p id="dialog-desc">This cannot be undone.</p>
  <menu>
    <li><button on:click={onClose}>Cancel</button></li>
    <li><button class="primary">Confirm</button></li>
  </menu>
</dialog>

<!-- showModal() makes the page behind inert. Initial focus follows the HTML dialog focusing steps (an autofocus element, else the dialog's focus delegate, else the dialog); on close, focus returns to the element focused before opening, if any. Verify Tab, Escape and initial focus in your target browsers, and choose the initial focus for your content and task. -->

Astro — Accessible island with ARIA

---
// Card.astro
export interface Props { title: string; href: string; description: string; }
const { title, href, description } = Astro.props;
---
<article aria-labelledby={`card-title-${title.replace(/\s/g,'-')}`}>
  <h3 id={`card-title-${title.replace(/\s/g,'-')}`}>
    <a href={href}>{title}</a>
  </h3>
  <p>{description}</p>
</article>

SolidJS — Accessible toggle button

import { createSignal } from 'solid-js';

export function ThemeToggle() {
  const [isDark, setIsDark] = createSignal(false);

  const toggle = () => {
    const next = !isDark();
    setIsDark(next);
    document.documentElement.dataset.theme = next ? 'dark' : 'light';
  };

  return (
    <button
      type="button"
      aria-pressed={isDark()}
      onClick={toggle}
    >
      <span aria-hidden="true">{isDark() ? '🌙' : '☀️'}</span> Dark mode
    </button>
  );
}

19. Native Mobile — Code Examples

SwiftUI (iOS / iPadOS / macOS)

// Accessible custom card
struct CourseCard: View {
    let title: String
    let instructor: String
    let duration: String
    let isCompleted: Bool

    var body: some View {
        VStack(alignment: .leading) {
            Text(title).font(.headline)
            Text(instructor).font(.subheadline)
            Text(duration).font(.caption)
        }
        .accessibilityElement(children: .combine)
        .accessibilityLabel("\(title), by \(instructor), \(duration)")
        .accessibilityHint(isCompleted ? "Completed" : "Double tap to open course")
        .accessibilityAddTraits(isCompleted ? .isSelected : [])
    }
}

// Accessible form field
struct EmailField: View {
    @Binding var email: String
    @State private var isInvalid = false

    var body: some View {
        VStack(alignment: .leading) {
            TextField("Email", text: $email)
                .textContentType(.emailAddress)
                .keyboardType(.emailAddress)
                .autocapitalization(.none)
                .accessibilityLabel("Email address")
                .accessibilityHint("Enter your email address")
                .accessibilityValue(isInvalid ? "Invalid email format" : "")
            if isInvalid {
                Text("Enter a valid email address")
                    .foregroundColor(.red)
                    .accessibilityLiveRegion(.assertive)
            }
        }
    }
}

// Respect system accessibility settings
@main
struct MyApp: App {
    @Environment(\.accessibilityReduceMotion) var reduceMotion
    @Environment(\.accessibilityInvertColors) var invertColors
    @Environment(\.sizeCategory) var sizeCategory  // Dynamic Type

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

Jetpack Compose (Android)

// Accessible card
@Composable
fun CourseCard(
    title: String,
    instructor: String,
    duration: String,
    isCompleted: Boolean,
    onClick: () -> Unit
) {
    Card(
        modifier = Modifier
            .clickable(onClick = onClick)
            .semantics {
                contentDescription = "$title, by $instructor, $duration" +
                    if (isCompleted) ", completed" else ""
                role = Role.Button
            }
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(title, style = MaterialTheme.typography.titleMedium)
            Text(instructor, style = MaterialTheme.typography.bodyMedium)
            Text(duration, style = MaterialTheme.typography.bodySmall)
        }
    }
}

// Accessible text field with error
@Composable
fun EmailField(email: String, onValueChange: (String) -> Unit, isError: Boolean) {
    OutlinedTextField(
        value = email,
        onValueChange = onValueChange,
        label = { Text("Email") },
        isError = isError,
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
        supportingText = {
            if (isError) {
                Text(
                    "Enter a valid email address",
                    modifier = Modifier.semantics { liveRegion = LiveRegionMode.Assertive }
                )
            }
        },
        modifier = Modifier
            .fillMaxWidth()
            .semantics {
                if (isError) error("Enter a valid email address")
            }
    )
}

// Reduce motion
@Composable
fun AnimatedContent() {
    val reduceMotion = LocalAccessibilityManager.current?.isReduceMotionEnabled ?: false
    val animationSpec = if (reduceMotion) snap() else tween(300)
    // use animationSpec in animations
}

20. User Preference Media Queries

Always respect OS/browser-level accessibility preferences. Never override them.

prefers-reduced-motion

/* Default: animate freely */
.card { transition: transform 0.3s ease; }

/* User opted out of motion */
@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
  /* Or provide an instant/fade alternative */
  .card { transition: opacity 0.1s linear; }
}
// JS: check before triggering animations
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!prefersReduced) { element.animate(keyframes, options); }

prefers-color-scheme (dark mode)

:root {
  --text: #1a1a1a;
  --bg: #ffffff;
}
@media (prefers-color-scheme: dark) {
  :root {
    --text: #f0f0f0;
    --bg: #121212;
  }
}
/* Re-check all contrast pairs in both modes */

prefers-contrast

/* High contrast mode request (not forced-colors) */
@media (prefers-contrast: more) {
  :root {
    --text: #000000;
    --bg: #ffffff;
    --border: #000000;
  }
  button { border: 2px solid #000; }
}

/* Reduced contrast (e.g. users with migraine) */
@media (prefers-contrast: less) {
  :root { --text: #444; }
}

forced-colors (Windows High Contrast Mode)

/* System replaces your colors — don't fight it */
@media (forced-colors: active) {
  /* Use system color keywords */
  button {
    border: 1px solid ButtonText;
    background: ButtonFace;
    color: ButtonText;
  }
  :focus-visible {
    outline: 2px solid Highlight;
  }
  /* Restore SVG icons hidden by forced-colors */
  svg { forced-color-adjust: none; fill: CanvasText; }
}

prefers-reduced-transparency

@media (prefers-reduced-transparency: reduce) {
  .overlay { background: rgba(0,0,0,0.9); /* instead of 0.5 */ }
  .frosted { backdrop-filter: none; background: var(--bg-solid); }
}

prefers-reduced-data (emerging)

@media (prefers-reduced-data: reduce) {
  /* Don't load decorative images or heavy fonts */
  .hero-bg { background-image: none; }
}

Dynamic Type / text resize

/* Use relative units everywhere — never px for type */
html { font-size: 100%; }   /* respect browser base */
body { font-size: 1rem; }
h1   { font-size: clamp(1.5rem, 5vw, 3rem); }

21. Alternative Input Methods

Users with motor disabilities may use input methods beyond keyboard and mouse. Design and test for all of them.

Switch Access (1–2 buttons)

  • iOS: Settings → Accessibility → Switch Control
  • Android: Settings → Accessibility → Switch Access
  • Behavior: single-switch or two-switch scanning through focusable elements
  • Requirements: logical focus order; no time-limited actions; adequate target size

Test yourself: enable Switch Control (iOS) or Switch Access (Android) with a Bluetooth keyboard spacebar as the switch.

Voice Control / Dragon NaturallySpeaking

  • iOS: Settings → Accessibility → Voice Control
  • Android: Voice Access app (Google)
  • Windows: Dragon NaturallySpeaking, Windows Voice Access
  • Users say element names or grid coordinates to activate elements

Requirements:

  • Visible label text must match (or be contained in) the accessible name — WCAG 2.5.3 Label in Name
  • Prefer labels that tell repeated controls apart when their context does not (for example several "Learn more" links); this is a usability recommendation for voice control, not a WCAG uniqueness requirement (see references/form-labels-and-names.md)
  • aria-label must contain the visible text, not replace it
<!-- ❌ Label in Name failure: aria-label replaces visible text -->
<button aria-label="Course details">Learn more</button>

<!-- ✅ aria-label extends visible text -->
<button aria-label="Learn more about HTML Semántico">Learn more</button>

Eye Tracking (Tobii, Eyegaze)

  • Users dwell on elements to activate them; no keyboard or pointer precision
  • Requirements: large targets (44×44pt minimum), no hover-only UI, no time-limited interactions, minimal cognitive load

Head Pointer / Mouth Stick

  • Like mouse but reduced precision
  • Requirements: adequate spacing between targets (WCAG 2.5.8), no drag-only interactions (WCAG 2.5.7)

Dictation (macOS built-in, Google Docs Voice Typing)

  • Passive dictation into text fields
  • Requirements: autocomplete attributes, no auto-correcting input values, visible editable boundaries

Cognitive accessibility (COGA)

  • Plain language: reading level ≤ grade 8 for general content
  • Consistent navigation and labeling (WCAG 3.2.3 / 3.2.4)
  • Error recovery: suggest corrections (3.3.3), allow undo (3.3.4)
  • No time limits or extend them (2.2.1)
  • Accessible authentication — avoid cognitive function tests in login steps unless an alternative, an assisting mechanism, or a 3.3.8 exception applies (WCAG 3.3.8)
  • Break complex processes into steps; show progress
  • Use icons alongside text for reinforcement

22. Legal Framework

Compliance is often mandatory. Key jurisdictions:

RegionLawStandardApplies toEffective
🇺🇸 USA (federal)Section 508WCAG 2.0 AAFederal ICT2017
🇺🇸 USA (private)ADA Titles II & IIIWCAG 2.1 AA (DOJ 2024 rule)State/local govt + public accommodations2024+
🇪🇺 EUEAA (Directive 2019/882)EN 301 549 / WCAG 2.1 AAB2C products & services28 Jun 2025
🇪🇺 EUWeb Accessibility DirectiveEN 301 549Public sector websites & appsIn force
🇬🇧 UKEquality Act + PSBAR 2018WCAG 2.1 AAPublic sectorIn force
🇨🇦 CanadaACAWCAG 2.1 AAFederal entitiesIn force
🇨🇦 OntarioAODAWCAG 2.0 AAPublic + large privateFull since 2021
🇦🇺 AustraliaDDA + Digital Service StandardWCAG 2.1 AAGovernmentActive
🇧🇷 BrazilLBI (Lei 13.146) + eMAGWCAG 2.1 AAPublic + private collective useActive
🇨🇱 ChileLey 20.422 + Decreto 1WCAG 2.0 AAGovernmentActive
🇲🇽 MexicoNOM-034 / Ley GIDWCAG 2.1 AAPublic sectorEvolving
🇪🇸 SpainRD 1112/2018 (WAD transposition)EN 301 549Public sectorActive
🇫🇷 FranceRGAA 4 + Loi 2005-102WCAG 2.1 AA via RGAAPublic + large companiesActive

Full table with enforcement and notes: data/legal-framework.csv Search: python scripts/search.py legal --jurisdiction "Chile"

EAA 2025 alert: Private sector digital products and services (e-commerce, banking, transport, e-books, streaming) serving EU consumers must comply by 28 June 2025.


23. Spanish ↔ English Glossary

Key terms for Spanish-speaking teams:

EnglishSpanish
accessibilityaccesibilidad
accessible namenombre accesible
accessibility treeárbol de accesibilidad
alt texttexto alternativo
assistive technologytecnología asistiva / tecnología de apoyo
color contrastcontraste de color
focus indicatorindicador de foco
focus managementgestión del foco
headingencabezado / título
keyboard traptrampa de teclado
landmarkpunto de referencia
live regionregión en vivo
screen readerlector de pantalla
semantic HTMLHTML semántico
skip linkenlace de salto
status messagemensaje de estado
success criterioncriterio de éxito
target sizetamaño de objetivo
WCAGPautas de Accesibilidad para el Contenido Web

Full glossary (70+ terms, with definitions and context): data/glossary-es.csv Search: python scripts/search.py glossary --keyword lector


24. Templates & Prompts

Templates (ready to fill)

FilePurpose
templates/audit-report.mdComplete audit report with finding blocks, severity scale, conformance table, retest plan
templates/site-audit-workbook.mdSite-audit evidence workbook: scope, page/state inventory, criterion matrix, findings (see references/site-audit-procedure.md)
templates/a11y-ci.ymlGitHub Actions pipeline: jest-axe + axe CLI + pa11y-ci + Playwright + Lighthouse
templates/vpat-2.5-template.mdOfficial VPAT 2.5 Rev WCAG — covers WCAG 2.x (A/AA/AAA), Section 508, EN 301 549

Prompts (copy & paste into your assistant)

FileWhen to use
prompts/audit-component.mdGet a full a11y audit of a component with WCAG SC + code fixes
prompts/sc-to-acceptance-criteria.mdConvert WCAG success criteria to Gherkin ACs for a user story
prompts/generate-alt-text.mdDraft alt text for informative or complex images
prompts/review-figma-handoff.mdReview a design handoff for a11y gaps before dev
prompts/summarize-audit-findings.mdExec-friendly summary of raw findings with 90-day plan
prompts/generate-aria-pattern.mdGenerate reference code for an accessible widget in any framework (review and test before shipping)
prompts/remediate-legacy-code.mdAudit + fix legacy HTML/jQuery/Bootstrap code for accessibility
prompts/generate-vpat-entry.mdTranslate audit findings into VPAT ACR language
prompts/test-with-screen-reader.mdGenerate step-by-step NVDA/VoiceOver/TalkBack test scripts
prompts/mobile-a11y-checklist.mdReact Native, SwiftUI, Jetpack Compose accessibility checklist
prompts/inclusive-ux-writing.mdReview UI copy for plain language and inclusive terminology
prompts/design-tokens-audit.mdAudit Figma/Style Dictionary tokens against WCAG contrast requirements

Accessible component examples (examples/components/)

FileWhat it demonstrates
examples/components/date-picker.htmlFull date picker: dialog pattern, roving tabindex, Arrow/Page/Home/End keys, typed input, live region errors
examples/components/toast-notifications.htmlToast system: role="status" (polite) + role="alert" (assertive), dismiss, reduced-motion support

25. Comprehensive Resource Repository

📚 See RESOURCES.md for a complete, curated repository of:

  • Official W3C/WAI standards & guidelines
  • Practical implementation guides (2024–2025)
  • Academic research & systematic reviews
  • Blogs, communities & education
  • Testing tools & frameworks (axe, WAVE, Lighthouse, etc.)
  • Recommended books & courses
  • Legal frameworks by jurisdiction
  • Case studies & accessible websites

26. Core References

Official Standards & Specifications

Practical Guides (2024–2025)

Academic Research

Blogs & Communities

Component Libraries & Pattern Libraries

Testing & Automation

Human Rights & Disability Models


25. Vue 3 Accessibility Examples

Accessible reactive form (Composition API)

<script setup lang="ts">
import { ref, computed } from 'vue';

const email = ref('');
const touched = ref(false);
const isInvalid = computed(() => touched.value && !/^[^@]+@[^@]+\.[^@]+$/.test(email.value));
const errorId = 'email-error';
const hintId  = 'email-hint';
</script>

<template>
  <form @submit.prevent="handleSubmit" novalidate>
    <div>
      <label for="email">
        Email address <span aria-hidden="true">*</span>
      </label>
      <input
        id="email"
        v-model="email"
        type="email"
        autocomplete="email"
        aria-required="true"
        :aria-invalid="isInvalid || undefined"
        :aria-describedby="[hintId, isInvalid ? errorId : ''].filter(Boolean).join(' ')"
        @blur="touched = true"
      />
      <span :id="hintId">We will never share your email.</span>
      <span
        v-if="isInvalid"
        :id="errorId"
        role="alert"
      >
        Enter a valid email address.
      </span>
    </div>
    <button type="submit" :disabled="isInvalid">Subscribe</button>
  </form>
</template>

Accessible modal (Vue 3 + Teleport)

<script setup lang="ts">
import { ref, watch, nextTick } from 'vue';
const props = defineProps<{ open: boolean }>();
const emit  = defineEmits<{ (e: 'close'): void }>();
const closeBtn = ref<HTMLButtonElement | null>(null);

watch(() => props.open, async (val) => {
  if (val) {
    await nextTick();
    closeBtn.value?.focus(); // Initial focus: Cancel, the least destructive action, because this cannot be undone (choose per task)
  }
});

function handleKeydown(e: KeyboardEvent) {
  if (e.key === 'Escape') emit('close');
}
</script>

<template>
  <!-- Teleport to body so the dialog is not inside a hidden or inert ancestor -->
  <Teleport to="body">
    <div
      v-if="open"
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
      aria-describedby="modal-desc"
      @keydown="handleKeydown"
    >
      <h2 id="modal-title">Confirm action</h2>
      <p id="modal-desc">This cannot be undone.</p>
      <button ref="closeBtn" @click="emit('close')">Cancel</button>
      <button class="primary">Confirm</button>
    </div>
    <!-- Backdrop -->
    <div v-if="open" aria-hidden="true" @click="emit('close')" />
  </Teleport>
</template>

This sketch moves focus and handles Escape only. It does not make the page behind inert, contain Tab or return focus: add those (or use native <dialog>.showModal() and check it in your browsers), returning focus to the opener or, if it is gone, to a logical element.

Accessible toggle button (Vue 3)

<script setup lang="ts">
import { ref } from 'vue';
const isDark = ref(false);
function toggle() {
  isDark.value = !isDark.value;
  document.documentElement.dataset.theme = isDark.value ? 'dark' : 'light';
}
</script>

<template>
  <button
    type="button"
    :aria-pressed="isDark"
    @click="toggle"
  >
    <span aria-hidden="true">{{ isDark ? '🌙' : '☀️' }}</span> Dark mode
  </button>
</template>

Live region (Vue 3)

<script setup lang="ts">
import { ref } from 'vue';
const announcement = ref('');
function save() {
  // ... save logic ...
  announcement.value = 'Document saved successfully.';
  setTimeout(() => { announcement.value = ''; }, 3000);
}
</script>

<template>
  <!-- Visually hidden live region -->
  <div aria-live="polite" aria-atomic="true" class="sr-only">
    {{ announcement }}
  </div>
  <button type="button" @click="save">Save</button>
</template>

26. React Native Accessibility

React Native accessibility maps to the same WCAG principles but uses platform-specific APIs.

Core props

import {
  View, Text, TouchableOpacity, TextInput,
  AccessibilityInfo, findNodeHandle
} from 'react-native';

// ── Accessible card (combines children into one focus target)
function CourseCard({ title, instructor, isCompleted }: Props) {
  return (
    <View
      accessible={true}                          // combine into single focus
      accessibilityLabel={`${title}, by ${instructor}`}
      accessibilityHint={isCompleted ? undefined : 'Double-tap to open course'}
      accessibilityRole="button"
      accessibilityState={{ selected: isCompleted }}
    >
      <Text>{title}</Text>
      <Text>{instructor}</Text>
    </View>
  );
}

// ── Accessible text input with error
function EmailInput({ value, onChange, error }: InputProps) {
  return (
    <View>
      <Text nativeID="email-label">Email address</Text>
      <TextInput
        value={value}
        onChangeText={onChange}
        accessibilityLabelledBy="email-label"
        accessibilityInvalidated={!!error}        // maps to aria-invalid
        accessibilityErrorMessage={error}          // RN 0.74+
        keyboardType="email-address"
        autoComplete="email"
        textContentType="emailAddress"            // iOS autofill
      />
      {error && (
        <Text
          accessibilityLiveRegion="assertive"     // announce immediately
          accessibilityRole="alert"
        >
          {error}
        </Text>
      )}
    </View>
  );
}

Touch targets (WCAG 2.5.8 equivalent)

import { StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  // iOS: minimum 44x44 pt; Android: minimum 48x48 dp
  touchable: {
    minWidth: 44,
    minHeight: 44,
    justifyContent: 'center',
    alignItems: 'center',
  },
  // hitSlop expands tap area without changing visual size
  // Use when the visual element must be smaller
});

// Use hitSlop for small icons
<TouchableOpacity
  hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
  accessibilityRole="button"
  accessibilityLabel="Delete item"
/>

Dynamic Type (font scaling)

import { Text } from 'react-native';

// ❌ Never: fixed font sizes block Dynamic Type
<Text style={{ fontSize: 16 }}>Static text</Text>

// ✅ Allow: system font that scales with user preference
<Text>Default text</Text>  // inherits system font size

// ✅ If you must set size, allow scaling
<Text allowFontScaling={true} style={{ fontSize: 16 }}>Scalable text</Text>

// ✅ maxFontSizeMultiplier: prevent runaway scaling in tight UIs
<Text maxFontSizeMultiplier={1.5} style={{ fontSize: 14 }}>Compact text</Text>

Reduce Motion

import { useEffect, useState } from 'react';
import { AccessibilityInfo } from 'react-native';
import Animated, { useSharedValue, withTiming, withSpring } from 'react-native-reanimated';

function AnimatedCard() {
  const [reduceMotion, setReduceMotion] = useState(false);
  const opacity = useSharedValue(0);

  useEffect(() => {
    AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotion);
    const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduceMotion);
    return () => sub.remove();
  }, []);

  useEffect(() => {
    opacity.value = reduceMotion
      ? withTiming(1, { duration: 0 })   // instant
      : withSpring(1);                    // animated
  }, [reduceMotion]);

  return <Animated.View style={{ opacity }} />;
}

Focus management (screen transition)

import { useRef, useEffect } from 'react';
import { View, AccessibilityInfo, findNodeHandle } from 'react-native';

function NewScreen() {
  const headingRef = useRef<View>(null);

  useEffect(() => {
    // Move VoiceOver/TalkBack focus to screen heading on mount
    const node = findNodeHandle(headingRef.current);
    if (node) AccessibilityInfo.setAccessibilityFocus(node);
  }, []);

  return (
    <View>
      <View
        ref={headingRef}
        accessible={true}
        accessibilityRole="header"
      >
        <Text>Settings</Text>
      </View>
    </View>
  );
}

Quick search: python scripts/search.py keys --reader TalkBack for TalkBack gestures, python scripts/search.py keys --reader VoiceOver --platform iOS for VoiceOver iOS.


27. Cognitive Accessibility (COGA)

Cognitive, language, and learning disabilities affect ~15% of people. WCAG addresses them partially — COGA supplemental guidance fills the gaps.

Top 10 COGA design patterns

PatternWhat to doWCAG mapping
Plain languageReading level ≤ grade 8; short sentences; active voice3.1.5 (AAA)
Familiar patternsStandard nav location; known icons; no novel interactions3.2.3 / 3.2.4
No time limitsExtend or remove timers; show countdown with aria-live2.2.1 / 2.2.3
Reduce cognitive load≤7 nav items; progressive disclosure; no autoplay3.2.2
Support memoryPre-fill known data; breadcrumbs; undo; don't re-ask (3.3.7)3.3.7 (WCAG 2.2)
Clear error recoverySay what happened + exactly what to do; inline validation3.3.1 / 3.3.3
Visible labelsPrefer a persistent visible <label>; a placeholder alone is a weak label (disappears on input, low contrast): judge against 3.3.2, see references/form-labels-and-names.md3.3.2
No distractionsprefers-reduced-motion; no autoplay audio2.2.2 / 2.3.1
Consistent vocabularySame word for same thing; glossary available3.1.3 (AAA)
Multiple input modesText + autocomplete; voice input; no drag-only (2.5.7)2.1.1 / 2.5.7

Accessible authentication (WCAG 3.3.8 — WCAG 2.2)

<!-- ⚠️ Cognitive function test (transcribing characters): conforms to 3.3.8 only if the step also
     provides an alternative method or an assisting mechanism. Judge the whole process, not this field. -->
<p>Type the characters you see in the image:</p>
<img src="captcha.png" alt="">
<input type="text" name="captcha">

<!-- Options that can support 3.3.8 (confirm they work in the real process) -->
<!-- 1. Allow paste and password managers in password fields -->
<input type="password" autocomplete="current-password">
<!-- 2. Offer another method (e.g. WebAuthn/passkeys, email link) -->
<!-- 3. Codes sent by email/SMS must be pasteable or autofillable, not only retyped -->
<!-- 4. An audio CAPTCHA that still requires transcribing is not an alternative -->

Full pattern database: data/cognitive-accessibility.csv (20 patterns) Search: python scripts/search.py cognitive --keyword memory


28. WCAG 3.0 — Draft status

WCAG 3.0 is an incomplete W3C Working Draft and not a standard. WCAG 2.1/2.2 AA remains the compliance target. The draft says it still has several years of work and that details will change; it also states that WCAG 3 does not replace WCAG 2, which will still be required in many places for a long time.

Sources (check them for the current status; reviewed 2026-10-01 against the 10 September 2026 Working Draft):

What not to state as fact

  • No WCAG 3 success criteria with fixed numbers (such as "3.1.1"): requirements in the draft are named (for example "Text contrast sufficient (minimum)") and may change.
  • Bronze, Silver and Gold do appear in section 4.1.4 of the draft, as the 4th–6th reporting tiers above conformance (the 3rd tier is conformance), but the number of supplemental requirements and assertions for each is still TBD. Section 4 is informative and exploratory, and the draft's editor's notes say the model is still under discussion (an alternative proposal uses scoring, with Bronze/Silver/Gold awarded after conformance). Present them as proposed reporting tiers in development, not as a finalized conformance target, and do not say what is required to reach them.
  • The draft does not use A/AA/AAA levels in the way WCAG 2 does; do not map WCAG 2 levels onto WCAG 3 tiers.
  • No contrast algorithm or thresholds: the draft says the contrast algorithm is yet to be determined. APCA is not confirmed as the WCAG 3 algorithm, so do not quote APCA Lc values as WCAG 3 requirements.
  • No finalization date.

How to prepare now

  1. Keep targeting WCAG 2.1/2.2 AA. Contrast compliance is measured with the WCAG 2.x ratios (1.4.3, 1.4.6, 1.4.11).
  2. Follow the draft at the links above before answering any WCAG 3 question, and label anything from it as draft.
  3. If a user asks about APCA, explain that it is a separate contrast model that is not part of WCAG 2.x conformance and not confirmed for WCAG 3.

The CLI command python scripts/search.py wcag3 and the MCP resource wcag://draft-wcag3 return only a draft-status notice with these links; they contain no criteria.


Notes for the agent

  1. Default to WCAG 2.1 AA and surface WCAG 2.2 additions when relevant (focus obscured, target size, drag alternatives, accessible auth, redundant entry, consistent help).
  2. Semantic HTML first; reach for ARIA only when native semantics are insufficient (Rule #1 of ARIA).
  3. Recommend real AT testing — automated tools catch ~20–40%.
  4. Include mobile whenever responsive or native is in scope; cite 44pt (iOS) / 48dp (Android).
  5. Always provide working code examples for recommended ARIA patterns and include focus management.
  6. Match tools to context: CI → axe-core/Playwright/jest-axe/cypress-axe; quick audit → axe DevTools, WAVE, Accessibility Insights; design review → Stark / Figma contrast plugins.
  7. Use the CSVs — prefer python scripts/search.py … over restating lookup tables. Point users at data/*.csv for exhaustive detail.
  8. Language: prefer person-first or identity-first per community norms; never pity-based framing.
  9. When asked about KPIs, handoff, audit, inclusive research, typography, or color palettes, use the relevant sections and CSVs — these are strong differentiators versus generic a11y advice.
  10. Reply in the user's language (English or Spanish) when they ask in Spanish; use data/glossary-es.csv for terminology. Terms like "lector de pantalla", "criterios de éxito", "tecnologías asistivas" are preferred.
  11. Stack examples: section 18 → Next.js, Angular, Svelte, Astro, SolidJS; section 19 → SwiftUI, Jetpack Compose; section 25 → Vue 3; section 26 → React Native.
  12. Legal questions: cite data/legal-framework.csv; always highlight the EAA 28 June 2025 deadline for EU-facing products.
  13. Cognitive accessibility: when ADHD, dyslexia, memory, or anxiety is mentioned, use section 27 and data/cognitive-accessibility.csv.
  14. WCAG 3.0: mention only as an incomplete W3C draft — WCAG 2.1/2.2 AA is still the standard. Do not cite WCAG 3 criteria IDs, APCA thresholds or dates as fact, and treat Bronze/Silver/Gold only as proposed reporting tiers; point to section 28 and the W3C draft.
  15. User preference media queries: recommend prefers-reduced-motion, prefers-contrast, forced-colors, prefers-reduced-transparency in every animation/color/theme discussion — see section 20.
  16. Templates & prompts: point users to templates/ and prompts/ for ready-to-use assets. The 12 prompts in prompts/ are designed to be pasted into an assistant directly; if it cannot read this repository's files, paste the relevant data/ rows or sections along with the prompt.
  17. External Resources: refer users to RESOURCES.md for comprehensive curated bibliography of books, blogs, research papers, tools, and legal frameworks — updated 2025 with WCAG 2.2, EAA deadline, and peer-reviewed studies.
  18. Reviewing code fragments (forms, controls, icon buttons, repeated names such as one "Delete" per row): read references/form-labels-and-names.md. Keep findings the code demonstrates apart from those that depend on context, need browser/keyboard/screen-reader/server verification, or are design recommendations; justify severity; do not report what a screen reader announced or an automated tool detected unless it was run; keep behavior shown by the code, described by a comment, merely possible and actually observed apart; cite a criterion only with the reason it applies.
  19. Authentication and modal dialogs: for 3.3.8 / 3.3.9 or modal focus questions read references/authentication-and-modal-focus.md. Do not call a whole authentication process conformant or non-conformant from one field, or from the mere presence of a password, CAPTCHA, paste or autofill; separate the requirement, a possible technique and what still needs checking. Offer initial-focus options by content and task rather than one fixed rule. Report behavior known only from a comment or description as described, not as observed.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.