Wcag Accessibility logo

Wcag Accessibility

Community
mrKanoh
wcag-accessibility

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".

Overview

PublishermrKanoh
Repositorywcag-accessibility-audit-skill
Skill namewcag-accessibility
Stars
8
Forks
1
Bundled files
54
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 54 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by mrKanoh on GitHub. Read the source before you install it.

Installation

Install the Wcag Accessibility AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/mrKanoh/wcag-accessibility-audit-skill/tree/main
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/mrKanoh/wcag-accessibility-audit-skill.git \
  .claude/skills/wcag-accessibility
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Wcag Accessibility in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Wcag Accessibility on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Wcag Accessibility is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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:

bash
# 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
templates/audit-report.mdComplete audit report template (fill-in)
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.

html
<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

html
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"

Essential properties & states

html
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

Modal Dialog

html
<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

html
<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

html
<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)

html
<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)

html
<!-- Action → button -->
<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>
  • 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:

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

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
css
/* 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.
css
/* 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 barArrow 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

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

bash
# axe-core CLI
npx @axe-core/cli https://example.com --tags wcag2a,wcag2aa,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

ts
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

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

test('homepage passes WCAG 2.2 AA', async ({ page }) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag22aa'])
    .analyze();
  expect(results.violations).toEqual([]);
});

Cypress + cypress-axe

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

Storybook a11y addon

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

GitHub Actions

yaml
- 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

tsx
// app/layout.tsx — announce route changes to screen readers
'use client';
import { usePathname } from 'next/navigation';
import { useEffect, useRef } from 'react';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const pathname = usePathname();
  const announceRef = useRef<HTMLParagraphElement>(null);

  useEffect(() => {
    if (announceRef.current) {
      announceRef.current.textContent = `Navigated to ${document.title}`;
    }
  }, [pathname]);

  return (
    <html lang="en">
      <body>
        {/* Visually hidden live region for route announcements */}
        <p
          ref={announceRef}
          aria-live="polite"
          aria-atomic="true"
          className="sr-only"
        />
        <a href="#main-content" className="skip-link">Skip to main content</a>
        {children}
      </body>
    </html>
  );
}

Angular — Accessible reactive form

typescript
// 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>

svelte
<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

astro
---
// 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

tsx
import { createSignal } from 'solid-js';

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

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

  return (
    <button
      type="button"
      aria-pressed={isDark()}
      aria-label={isDark() ? 'Switch to light mode' : 'Switch to dark mode'}
      onClick={toggle}
    >
      {isDark() ? '☀️' : '🌙'}
    </button>
  );
}

19. Native Mobile — Code Examples

SwiftUI (iOS / iPadOS / macOS)

swift
// 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)

kotlin
// 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

css
/* 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
// 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)

css
: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

css
/* 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)

css
/* 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

css
@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)

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

Dynamic Type / text resize

css
/* 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
html
<!-- ❌ 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/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 production-ready accessible widget in any framework
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)

vue
<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)

vue
<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)

vue
<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"
    :aria-label="isDark ? 'Switch to light mode' : 'Switch to dark mode'"
    @click="toggle"
  >
    {{ isDark ? '☀️' : '🌙' }}
  </button>
</template>

Live region (Vue 3)

vue
<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

tsx
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)

tsx
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)

tsx
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

tsx
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)

tsx
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)

html
<!-- ⚠️ 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.

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Wcag Accessibility AI skill do?

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,...

Why use Wcag Accessibility on TypingMind?

Because you install it once and use it with any model. Wcag Accessibility is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Wcag Accessibility in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/mrKanoh/wcag-accessibility-audit-skill/tree/main. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Wcag Accessibility?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Wcag Accessibility?

As many as you like. As long as a model supports skills, you can use Wcag Accessibility with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Wcag Accessibility AI skill free?

Yes. It is published on GitHub by mrKanoh under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇