SuperAPI AI Directory

awesome-accessibility-audit

Testing & quality

View repository

Updated 10-16 11:17

About this skill

Accessibility Audit

Review UI and markup for accessibility and give the concrete fix for each finding, aligned with WCAG, so all users can perceive, operate, and understand the interface.

Read-only: it reports findings, each with the markup or attribute change that resolves it, and never applies them. A snippet in a report is reviewable next to the criterion it satisfies; the same edit applied silently is not. Hand the report to whoever owns the component.

When to Activate

  • User asks for "accessibility", "a11y", "WCAG", or "screen reader support"
  • Before shipping a new page or component
  • Reviewing forms, modals, or interactive UI
  • After a design or UI change that affects interaction or content

Work Process

  1. Define scope — Page, component, or flow to review (e.g. login form, report table, modal dialog).
  2. Check each area — Semantic HTML, keyboard, labels and names, color and contrast, dynamic content, motion. Use the checklists below.
  3. Document findings — Location (component/file or element type), WCAG criterion or principle, issue, impact, and recommended fix. Severity: Critical / High / Medium.
  4. Give the fix, do not apply it — A code snippet or attribute change in the report where possible. Prefer native HTML and correct ARIA over custom widgets when they suffice.
  5. Recommend follow-up — Suggest automated tools (e.g. axe, Lighthouse, pa11y; in CI, fail the build on critical) and manual testing for broader coverage. Automated tools catch roughly 30–40% of WCAG issues — the rest is manual. Recommend a flow × assistive-tech matrix: test each key user flow across keyboard-only, a screen reader (VoiceOver on Mac/iOS, NVDA on Windows, TalkBack on Android), 200% and 400% zoom / reflow (WCAG 1.4.10), Windows High Contrast, and reduced-motion; note Voice Control/Dragon and Switch Control where relevant. Do not claim full WCAG compliance from a single review.
    • For automated assertions in tests: axe-core with .withTags(['wcag2a','wcag2aa']) targets a specific WCAG tier and can fail CI on violations; Playwright's page.ariaSnapshot() (1.59+) asserts the accessibility tree of dialogs, menus, and composite widgets — coverage beyond what axe/Lighthouse give.
    • Run automated scans at more than one viewport (mobile and desktop breakpoints, not only the default): focus-obscured, overlapping targets, and reflow violations appear or vanish with viewport size, so a single-viewport scan silently under-reports.

Done when: the scope is stated, every checklist area below has been walked, each finding carries its location, its WCAG criterion and its fix, and whatever only a screen reader or a second viewport could settle is named as untested rather than assumed.

Focus Areas and Checklist

1. Semantic HTML and structure

  • One <h1> per page; heading levels in order (no skipping 2 → 4).
  • Skip link to main content as the first focusable element (WCAG 2.4.1 Bypass Blocks).
  • Viewport meta does not disable zoom: flag user-scalable=no and maximum-scale=1 (WCAG 1.4.4).
  • Every page has a <title> that describes its topic or purpose, and it changes on SPA route changes (2.4.2 Page Titled, A).
  • Page language is set with lang on <html> (3.1.1, A); passages in another language carry their own lang (3.1.2 Language of Parts, AA).
  • Content is not locked to one orientation (a CSS or manifest orientation lock) unless the orientation is essential (1.3.4 Orientation, AA).
  • Reflow: at 320 CSS px wide (400% zoom of a 1280px viewport) content needs no two-dimensional scrolling, except for data tables, maps, and similar content that needs both (1.4.10, AA).
  • Text spacing: with line height 1.5, paragraph spacing 2x, letter spacing 0.12em and word spacing 0.16em applied, nothing clips, overlaps, or disappears; flag fixed-height text containers and overflow: hidden on text (1.4.12, AA).
  • Landmarks: <main>, <nav>, <aside>, <footer> where appropriate.
  • Lists use <ul>/<ol>/<li>; tables use <th>, scope, and caption if applicable.
  • Buttons are <button> or <input type="submit">; links are <a href="...">. Do not use <div onclick> for actions without role, keyboard, and focus.
  • Form controls have correct type and grouping (<fieldset>, <legend> for groups).

2. Keyboard

  • All interactive elements are focusable (no tabindex="-1" on buttons/links unless managed for modal).
  • Focus order is logical (matches visual order or is explicitly managed).
  • Visible focus indicator (outline or custom style); not removed with outline: none without replacement. Prefer :focus-visible (no ring on mouse click) and :focus-within for compound controls.
  • No keyboard trap: user can tab out of modals and menus; Escape closes where expected.
  • Custom widgets (tabs, accordions, menus) operable with keyboard (Enter/Space to activate, Arrow keys if applicable).
  • Content shown on hover or focus (tooltips, popovers, submenus) is dismissible without moving the pointer (Escape), hoverable (the pointer can move onto it without it vanishing), and persistent until dismissed or no longer relevant (1.4.13, AA). A feature reachable only by hover has a keyboard and touch path.

3. Labels and names

  • Every form input has a visible <label> or aria-label; labels are associated (e.g. for/id or wrapping).
  • Inputs carry correct type/inputmode and autocomplete attributes (WCAG 1.3.5 Identify Input Purpose).
  • Form errors appear inline next to their fields, each associated to its field via aria-describedby (without it a screen reader doesn't read the error when the field gets focus). On submit, focus moves to the first error; for long forms, add a keyboard-focusable error summary at the top that links to each errored field (GOV.UK pattern).
  • User input is preserved on submit error, not cleared (reduces re-entry burden; see 3.3.7). Validate on blur, not on every keystroke.
  • Paste is never blocked (onPaste + preventDefault is an anti-pattern) — anywhere, not just password fields.
  • Buttons and links have clear, unique names (text content or aria-label). No "Click here" or "Read more" without context.
  • Label in name: the accessible name of a control contains its visible text, ideally starting with it, so voice-control users can say what they see (2.5.3, A). An aria-label that replaces visible text with different words fails.
  • Images: meaningful images have alt describing content; decorative images have alt="" or role="presentation". Decorative icons (inline SVG, icon fonts) get aria-hidden="true".
  • Iframe and embedded content have title or aria-label.

4. Color and contrast

  • Text meets contrast ratio (e.g. 4.5:1 for normal text, 3:1 for large; 3:1 for UI components). Check against background. (Newer design systems may use APCA, the perceptual model slated for WCAG 3.0 — note it if the project targets APCA, but audit against WCAG 2.2 ratios unless told otherwise.)
  • Information is not conveyed by color alone (e.g. required fields, errors, status). Use icon, text, or pattern as well.
  • Focus indicator is visible and not reliant only on color change.

5. Dynamic content and focus

  • Content that appears or updates (e.g. toast, live results) is announced: aria-live="assertive" only for urgent interruptions (errors, toasts requiring action); aria-live="polite" for feed updates and result counts.
  • SPA route changes are silent to screen readers: announce the new page via an aria-live="polite" region or move focus to the new page's <h1>.
  • Modals and dialogs: focus moves into the modal when opened; focus is trapped inside; focus returns to trigger when closed; first focusable element or explicit autoFocus per pattern.
  • State is communicated: expanded/collapsed (aria-expanded), selected (aria-selected), current (aria-current), disabled (disabled or aria-disabled).

6. Motion, media and time

  • If the project supports it: respect prefers-reduced-motion (disable or reduce animation). Optional but recommended for vestibular sensitivity.
  • Nothing flashes more than three times in any one second (2.3.1 Three Flashes or Below Threshold, A); check animated GIFs, video, and canvas effects.
  • Anything that moves, blinks, or scrolls automatically for more than 5 seconds alongside other content (carousels, tickers, autoplay background video) has a pause, stop, or hide control; auto-updating content has one too (2.2.2, A).
  • Audio that plays automatically for more than 3 seconds can be paused or stopped, or its volume controlled independently of system volume (1.4.2, A).
  • Prerecorded video with sound has captions (1.2.2, A) and audio description or a text alternative for visual-only information (1.2.3, A; 1.2.5 audio description, AA); live video with sound has captions (1.2.4, AA); audio-only and video-only media have a transcript or equivalent (1.2.1, A). Check the player controls themselves are keyboard operable and labelled.

7. WCAG 2.2 additions (often missed)

  • 2.4.11 Focus not obscured — sticky headers/footers must not hide the focused element (scroll-margin-top helps).
  • 2.5.8 Target size — interactive targets at least 24×24 CSS px, or sufficient spacing between smaller ones. When the visible control is smaller, extend the hit area to 40–44px with a pseudo-element; two interactive elements must never have overlapping hit areas; a checkbox/radio and its label share one continuous target.
  • 2.5.7 Dragging movements — every drag operation has a click/tap alternative.
  • 3.3.7 Redundant entry — don't ask for the same information twice within one flow.
  • 3.3.8 Accessible authentication — no cognitive test to log in; paste allowed in password fields; passkey or email-link alternative offered.
  • 3.2.6 Consistent help — help mechanism appears in the same place on every page.

Output Format

For each issue:

**[Location: file or component/element]**
- **Issue:** [What is wrong.]
- **WCAG / principle:** [Criterion or principle, e.g. 1.3.1 Info and Relationships, 2.1.1 Keyboard.]
- **Impact:** [Who is affected and how.]
- **Recommendation:** [Concrete fix: code or attribute. For contrast findings, cite computed ratios: "#aaa on #fff = 2.32:1, needs 4.5:1 → use #595959 (7.0:1)".]
- **Severity:** Critical | High | Medium

Example of a populated finding:

**[src/components/LoginForm.tsx — error message]**
- **Issue:** The "Invalid email" error renders visually below the field but is not associated with the input and is never announced.
- **WCAG / principle:** 3.3.1 Error Identification; 4.1.3 Status Messages.
- **Impact:** A screen-reader user submits, hears nothing, and cannot tell the form failed or which field to fix.
- **Recommendation:** Link it: `<input aria-describedby="email-err" aria-invalid="true">` + `<p id="email-err" role="alert">Invalid email</p>`; on submit, move focus to the first invalid field.
- **Severity:** Critical

Summary: "Reviewed: [scope]. Found X critical, Y high, Z medium. Recommend automated scan (axe/Lighthouse) and keyboard/screen reader testing."

Severity

  • Critical — Blocks core task (e.g. cannot submit form, cannot navigate with keyboard, no labels on required fields). Fix before release.
  • High — Significant barrier (e.g. poor contrast, missing headings, confusing order). Fix soon.
  • Medium — Improvement (e.g. redundant label, minor contrast). Backlog or fix when touching the component.

Good vs bad examples

Button:

<!-- BAD -->
<div onclick="submit()">Submit</div>

<!-- GOOD -->
<button type="submit">Submit</button>

Image:

<!-- Decorative -->
<img src="decoration.svg" alt="" role="presentation">

<!-- Meaningful -->
<img src="chart.png" alt="Bar chart showing revenue up 20% in Q4">

Form:

<!-- BAD -->
<input type="email" placeholder="Email">

<!-- GOOD -->
<label for="email">Email</label>
<input id="email" type="email" placeholder="you@example.com">

When a rule breaks

Each checklist item encodes a default that prevents the most common failure mode. When the context inverts the failure mode, the rule may invert too — a decorative element that a screen reader should skip wants aria-hidden, not a label; a native <button> must not also get a keydown handler (double activation). The work is recognizing the inversion, not memorizing exceptions. Never let an exception breach the correctness/a11y floor: keyboard operability, a programmatic name, and sufficient contrast are not negotiable.

Rules

  • Do not claim full compliance — Frame as "issues found in reviewed scope." Recommend automated and manual testing for full coverage.
  • Prefer native HTML — Use <button>, <label>, <main>, etc. Use ARIA when semantics cannot be expressed with HTML (e.g. aria-expanded on a custom accordion).
  • Component libraries — If the project uses one (e.g. Radix, MUI), note library a11y patterns (focus trap, roles) and ensure they are used correctly rather than reimplementing.

Checklist (before finishing)

  • Scope clearly defined
  • Each finding has location, issue, WCAG reference, recommendation, severity
  • At least semantic structure, keyboard, and labels/names covered
  • Follow-up (automated + manual testing) suggested
  • No "fully WCAG 2.1 AAA compliant" without full audit

Anti-patterns

Anti-pattern Better approach
Only checking color contrast Cover keyboard, labels, structure, and dynamic content
Suggesting "add aria-label" everywhere Prefer visible labels and semantic HTML; use ARIA when necessary
Ignoring focus order in modals Document focus trap and return focus behavior
Claiming compliance after code review only Recommend axe/Lighthouse and real assistive tech testing