Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions .claude/skills/tina4-a11y/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
---
name: tina4-a11y
description: Use when a project needs a WCAG 2.1 Level AA accessibility audit and fix pass — semantic landmarks, keyboard navigation, focus indicators, ARIA states, colour contrast, forms, zoom/reflow, reduced motion, touch targets, and screen reader support. Works on any Tina4 project (PHP, Python, Ruby, Node) or plain HTML. Reads DESIGN.md if present to avoid re-asking for known facts. Triggers: "audit accessibility", "WCAG audit", "a11y check", "make this accessible", "screen reader support", "keyboard navigation", "focus indicators", "ARIA", "aria labels", "our site is not accessible".
updated_for_version: 1.0.0
---

# tina4-a11y — WCAG 2.1 AA accessibility from audit to fix

> 🤖♿ **Skill-active marker.** Begin every reply with 🤖♿ while this skill is guiding the session. Drop it only once the conversation has clearly moved off accessibility into other work.

You are the accessibility lead for this project. Accessibility means the site is usable by people who rely on keyboard navigation, screen readers, high-contrast displays, reduced-motion settings, or magnification — and it improves SEO, legal compliance, and general usability for everyone. Your job is to audit what exists against WCAG 2.1 Level AA, generate the fixes, and inject them into the project's template and stylesheet layer systematically. Every output is real, executable code — no placeholders.

## Contents

Read top to bottom once, then jump by section. Orientation and the phase order live here; the phase bodies live in `references/` — each file is named in the workflow table and the phase stubs below.

**Orientation**
- When you fire - and when you do not
- First action - read `design/DESIGN.md` before asking anything
- The audit-to-fix workflow (20 phases) - Working reflexes
- **Degrees of freedom** - what is inviolable vs. a default vs. your judgement (read this next)

**Reference files** (all in `references/`, one level deep)
- `discovery-and-scope.md` - Phase 1 (discovery audit + scope checkpoint)
- `landmarks-and-structure.md` - Phase 2 + Phase 14
- `keyboard-and-focus.md` - Phase 3 + Phase 4
- `images-and-media.md` - Phase 5
- `forms.md` - Phase 6
- `aria-and-live-regions.md` - Phase 7 + Phase 10 + Phase 11
- `contrast-and-colour.md` - Phase 8
- `interactive-semantics.md` - Phase 9
- `zoom-reflow-motion.md` - Phases 12, 13, 15, 16, 17, 18, 19
- `validation-and-handoff.md` - Phase 20, DESIGN.md section, plan structure, skill relationships, avoid-list

## Degrees of freedom

Not every line here carries the same weight. Knowing which is which lets you move fast without
breaking what must not break. Three tiers:

- 🔒 **Non-negotiable - never skip, however small the task.**
**Read `design/DESIGN.md` first** (focus-ring token, contrast ratios, touch-target minimum, motion
policy); never re-ask for facts it holds. **Audit before you fix.** **Cite the specific WCAG
criterion** on every finding (e.g. WCAG 2.1 SC 1.4.3 Contrast Minimum) - vague "accessibility
problem" is not a report. **Every output is real, executable code - no placeholders**; every finding
carries file path, line number, current state, WCAG criterion, corrected code, and the patch to
apply. **Fix the CRITICAL tier first** (Phases 2-6), then HIGH, MEDIUM, OPTIONAL. **Keyboard-first.**
**Prefer semantic HTML over ARIA patches.** **No colour-only signals.** **Never silently substitute
a failing brand colour** - flag, propose, wait for the user. **Re-run safely** - no duplicate fixes;
the scope checkpoint always fires so the user steers. **The 🤖♿ skill-active marker** on every reply.

- 🎚️ **Default with a reason - follow unless this project genuinely differs.**
The 20-phase order and its CRITICAL / HIGH / MEDIUM / OPTIONAL tiering; the deliverable set (fixes
injected into templates and stylesheets, the handoff report, the `## Accessibility (WCAG 2.1 AA)`
section appended to `design/DESIGN.md`, `plan/a11y/PLAN.md`); the 44×44px touch-target target over
the WCAG-only 24×24 minimum; the `:focus-visible` box-shadow ring pattern; `prefers-reduced-motion`
duration tokens redefined to 0ms; the avoid-list of accessibility anti-patterns. Depart deliberately,
name the reason, and record it.

- 🧭 **Judgement - read the task and choose.**
How deep the scope goes (fix everything vs AA-required vs critical-only vs user-scoped); which
automated tools to run; which pages count as key pages for the manual walkthroughs; ask-first vs
decide-and-proceed on a given fix; verbosity. The skill gives the heuristic; you read the situation.

## When you fire

- "Audit accessibility" / "a11y check" / "make this accessible"
- "WCAG audit" / "WCAG 2.1 AA compliance"
- "Screen reader support" / "keyboard navigation" / "focus indicators"
- "ARIA" / "aria labels" / "aria-expanded"
- "Our site is not accessible" / "legal compliance for the web"
- "tina4-a11y" (explicit invocation)

Do NOT fire for:
- Visual design decisions — those belong to tina4-design
- SEO or AI discovery — those belong to tina4-seo (though semantic HTML, alt text, page titles overlap)
- General bug fixes not related to accessibility

## First action — read design/DESIGN.md

Before asking a single question, check for `design/DESIGN.md` (the tina4-design deliverable folder). If it exists, read it. It contains the token system, focus ring definition, contrast ratios, touch target minimum, and motion personality — facts that were already gathered by tina4-design. Never ask for information that is already in `design/DESIGN.md`.

If `design/DESIGN.md` does not exist, check the project root for a legacy `DESIGN.md`; if neither exists, note it and gather the facts you need directly from the code.

---

## Working reflexes

- **🔍 Audit before you fix.** Read the actual templates and stylesheets before writing any change. Never assume the current state from context alone.
- **📐 Real WCAG references.** Every finding cites the specific WCAG criterion (e.g. WCAG 2.1 SC 1.4.3 Contrast Minimum). Vague "accessibility problem" is not a report.
- **⌨️ Keyboard-first.** Every interactive element must be operable by keyboard alone before it is operable by mouse. If you can't tab to it, focus it, and activate it — it is broken.
- **🎯 Fix at the source.** Prefer semantic HTML fixes over ARIA patches. A `<button>` beats `<div role="button">`. A native `<dialog>` beats a hand-rolled focus trap.
- **📣 Show the work.** Every finding includes: file path, line number, current state, WCAG criterion, corrected code, and the exact patch to apply.
- **🚫 No colour-only signals.** Every semantic meaning conveyed by colour (red = error, green = success) must also carry a text label, icon, or pattern.
- **♻️ Re-run safely.** Running this skill on a project a second time must not duplicate fixes. Check for existing accessibility work before adding.

---

## The audit-to-fix workflow

Twenty phases, tiered by priority. Phase 1 discovers and scopes; Phases 2-6 are CRITICAL; the rest run in priority order. Read the matching reference before writing fixes for a phase.

| Phase(s) | Name | Tier | Reference |
|----------|------|------|-----------|
| 1 | Discovery audit + scope checkpoint | — | `references/discovery-and-scope.md` |
| 2 | Page structure and landmarks | CRITICAL | `references/landmarks-and-structure.md` |
| 3 | Keyboard navigation | CRITICAL | `references/keyboard-and-focus.md` |
| 4 | Visible focus indicators | CRITICAL | `references/keyboard-and-focus.md` |
| 5 | Images and non-text content | CRITICAL | `references/images-and-media.md` |
| 6 | Forms | CRITICAL | `references/forms.md` |
| 7 | ARIA states and properties | HIGH | `references/aria-and-live-regions.md` |
| 8 | Colour contrast | HIGH | `references/contrast-and-colour.md` |
| 9 | Interactive element semantics | HIGH | `references/interactive-semantics.md` |
| 10 | Live regions and dynamic content | MEDIUM | `references/aria-and-live-regions.md` |
| 11 | Tables | MEDIUM | `references/aria-and-live-regions.md` |
| 12 | Reduced motion | MEDIUM | `references/zoom-reflow-motion.md` |
| 13 | Touch target sizes | MEDIUM | `references/zoom-reflow-motion.md` |
| 14 | Heading hierarchy | MEDIUM | `references/landmarks-and-structure.md` |
| 15 | Zoom and reflow (incl. Orientation 1.3.4) | HIGH | `references/zoom-reflow-motion.md` |
| 16 | Text spacing | HIGH | `references/zoom-reflow-motion.md` |
| 17 | Time and motion limits | MEDIUM | `references/zoom-reflow-motion.md` |
| 18 | Enhanced screen reader support | OPTIONAL | `references/zoom-reflow-motion.md` |
| 19 | High contrast mode (Forced Colors) | OPTIONAL | `references/zoom-reflow-motion.md` |
| 20 | Validation and handoff | — | `references/validation-and-handoff.md` |

---

## Phase stubs

**Phase 1 - Discovery audit:** read `references/discovery-and-scope.md` (read DESIGN.md, read templates/stylesheets, run automated tools, report the ✅/❌/⚠️ table, then the scope checkpoint — wait for the user signal before writing any fixes).

**Phase 2 - Page structure and landmarks (CRITICAL):** read `references/landmarks-and-structure.md` (`<html lang>`, landmark roles, skip link, unique page titles, `aria-current="page"`).

**Phase 3 - Keyboard navigation (CRITICAL):** read `references/keyboard-and-focus.md` (tab order audit, `tabindex` rules, keyboard operability for custom components, focus trap — prefer native `<dialog>`).

**Phase 4 - Visible focus indicators (CRITICAL):** read `references/keyboard-and-focus.md` (replace every `outline: none`, the `:focus-visible` box-shadow pattern, 3:1 against adjacent).

**Phase 5 - Images and non-text content (CRITICAL):** read `references/images-and-media.md` (`<img>` alt text, decorative vs meaningful `<svg>`, CSS background images, video and audio).

**Phase 6 - Forms (CRITICAL):** read `references/forms.md` (labels, required fields, error messages, grouped controls, submit button, autocomplete tokens).

**Phase 7 - ARIA states and properties (HIGH):** read `references/aria-and-live-regions.md` (required ARIA per component, `aria-atomic` on live regions).

**Phase 8 - Colour contrast (HIGH):** read `references/contrast-and-colour.md` (contrast formula, pairs to test, failing-accent decision, colour is not the only indicator).

**Phase 9 - Interactive element semantics (HIGH):** read `references/interactive-semantics.md` (buttons vs links, icon-only buttons, link purpose, `title`, Label in Name 2.5.3, content on hover/focus 1.4.13).

**Phase 10 - Live regions and dynamic content (MEDIUM):** read `references/aria-and-live-regions.md` (`aria-live` regions, loading states, form submission feedback).

**Phase 11 - Tables (MEDIUM):** read `references/aria-and-live-regions.md` (`<caption>`, `<th>` + `scope`, `<thead>`/`<tbody>`, layout tables).

**Phase 12 - Reduced motion (MEDIUM):** read `references/zoom-reflow-motion.md` (global CSS rule, JS-driven animation gate, auto-playing carousels).

**Phase 13 - Touch target sizes (MEDIUM):** read `references/zoom-reflow-motion.md` (44×44 target vs WCAG 24×24, pad rather than resize).

**Phase 14 - Heading hierarchy (MEDIUM):** read `references/landmarks-and-structure.md` (one `<h1>`, no skipped levels, structure not visual size).

**Phase 15 - Zoom and reflow (HIGH):** read `references/zoom-reflow-motion.md` (320px width, 400% zoom, fixed overlays, Orientation 1.3.4).

**Phase 16 - Text spacing (HIGH):** read `references/zoom-reflow-motion.md` (user-override spacing, replace fixed `height` with `min-height`).

**Phase 17 - Time and motion limits (MEDIUM):** read `references/zoom-reflow-motion.md` (auto-updating content 2.2.4, session timeouts 2.2.1, no flashing 2.3.1).

**Phase 18 - Enhanced screen reader support (OPTIONAL):** read `references/zoom-reflow-motion.md` (breadcrumbs, pagination, status badges, `aria-describedby`, PDF/document links, `target="_blank"`).

**Phase 19 - High contrast mode (OPTIONAL):** read `references/zoom-reflow-motion.md` (`@media (forced-colors: active)` to restore indicators).

**Phase 20 - Validation and handoff:** read `references/validation-and-handoff.md` (keyboard walkthrough, screen reader walkthrough, re-run tools, handoff report, DESIGN.md accessibility section, `plan/a11y/PLAN.md`, relationship to other skills, avoid-list).
70 changes: 70 additions & 0 deletions .claude/skills/tina4-a11y/references/aria-and-live-regions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# ARIA, live regions and tables

> Reference for the `tina4-a11y` skill: Phase 7 (ARIA states and properties, HIGH), Phase 10 (live regions and dynamic content, MEDIUM) and Phase 11 (tables, MEDIUM). Moved out of `SKILL.md` unchanged.

## Phase 7 — HIGH PRIORITY: ARIA states and properties

ARIA states communicate dynamic changes to assistive technology. Without them, interactive components are invisible to screen readers.

Check every custom interactive component for these required attributes:

| Component | Required ARIA | Notes |
|-----------|--------------|-------|
| Accordion header button | `aria-expanded="true/false"` | Changes on toggle |
| Dropdown trigger | `aria-expanded`, `aria-haspopup="listbox"` or `"menu"` | |
| Modal / dialog | `role="dialog"`, `aria-modal="true"`, `aria-labelledby` (pointing to title) | Native `<dialog>` handles most of this |
| Tab list | `role="tablist"`, `role="tab"`, `role="tabpanel"`; `aria-selected` on tabs; `aria-controls` linking tab to panel | |
| Alert / error | `role="alert"` (errors) or `aria-live="polite"` (status) | |
| Progress bar | `role="progressbar"`, `aria-valuenow`, `aria-valuemin`, `aria-valuemax` | |
| Toggle / switch | `role="switch"`, `aria-checked="true/false"` | |
| Loading spinner | `role="status"`, `aria-label="Loading"`, `aria-live="polite"` | |
| Tooltip | `role="tooltip"` on tooltip element; `aria-describedby` on trigger | |
| Sortable table header | `aria-sort="ascending/descending/none"` | |
| Selected item in list | `aria-selected="true"` | |
| Disabled control | `aria-disabled="true"` (for custom elements); native `disabled` for real inputs | |

Find each component type in the project, check for the required attributes, and add any missing.

**`aria-atomic` on live regions.** By default screen readers announce only changed content within a live region. If the region's whole content forms a single message (e.g. a stat update: "42 unread"), add `aria-atomic="true"` so the full region is announced.

---

## Phase 10 — MEDIUM PRIORITY: Live regions and dynamic content

Content that updates without a page reload must be announced to screen readers.

### 10.1 `aria-live` regions

- Status messages, success confirmations, loading complete: `aria-live="polite"`
- Error messages, urgent alerts: `aria-live="assertive"` (use sparingly — it interrupts)
- Toast / snackbar container: `aria-live="polite"` + `role="status"`

The live region must exist in the DOM BEFORE content is injected. Creating and injecting the region simultaneously misses the announcement.

### 10.2 Loading states

```html
<div role="status" aria-live="polite" aria-label="Loading">
<!-- spinner -->
</div>
```

Remove or hide when done and announce completion.

### 10.3 Form submission feedback

After a form submits (success or error), the message must be in a live region OR focus must move to the message so it is announced.

---

## Phase 11 — MEDIUM PRIORITY: Tables

Data tables must be structured so screen readers can understand cell-to-header relationships.

1. Every `<table>` used for tabular data must have:
- `<caption>` describing the table (visually hidden with `.sr-only` is fine if a visible heading is nearby)
- `<th>` for header cells, never `<td>` styled to look like a header
- `scope="col"` on column headers, `scope="row"` on row headers
- `<thead>` and `<tbody>` (and `<tfoot>` where appropriate)

2. Tables used for layout must have `role="presentation"` or `aria-hidden="true"` — but prefer replacing them with CSS grid or flexbox entirely.
52 changes: 52 additions & 0 deletions .claude/skills/tina4-a11y/references/contrast-and-colour.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Contrast and colour

> Reference for the `tina4-a11y` skill: Phase 8 (colour contrast, HIGH). Moved out of `SKILL.md` unchanged.

## Phase 8 — HIGH PRIORITY: Colour contrast

WCAG 2.1 AA requires:
- **4.5:1** for normal text (< 18pt regular or < 14pt bold)
- **3:1** for large text (≥ 18pt regular or ≥ 14pt bold) and UI components/icons

### 8.1 Compute the contrast ratios

Use this formula on every text/background token pair from DESIGN.md:

```js
function linearize(c) {
const s = c / 255;
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
}
function luminance({ r, g, b }) {
return 0.2126 * linearize(r) + 0.7152 * linearize(g) + 0.0722 * linearize(b);
}
function contrast(rgb1, rgb2) {
const [L1, L2] = [luminance(rgb1), luminance(rgb2)];
const [hi, lo] = L1 > L2 ? [L1, L2] : [L2, L1];
return ((hi + 0.05) / (lo + 0.05)).toFixed(2) + ':1';
}
```

### 8.2 Pairs to test at minimum

- Body text on page background
- Body text on card / surface background
- Link text on page background
- Placeholder text on input background (3:1 minimum)
- Button text on button background (all variants: primary, secondary, destructive)
- Badge text on badge background (all semantic colours)
- Disabled text (may fall below 4.5:1 — noted as an exception)

For any failing pair, propose a corrected hex value that passes while staying as close as possible to the original brand colour. Never silently substitute — flag it, propose, get confirmation.

**Ask the user which fix to apply** using a structured-question tool (`AskUserQuestion` in Claude Code, or the equivalent). For a failing brand accent, offer three concrete options with the exact hex values in each label:

- **Darken the accent** — replace `#FAB033` with `#E89A20` (contrast [X]:1) across the token system
- **Pair with a darker text token** — keep `#FAB033`, use `--text-1` instead of accent on those surfaces (contrast [Y]:1)
- **Keep as-is, log exception** — record in DESIGN.md that this pair is an accepted deviation

Fallback if no structured tool is available: send the same three options as a numbered prose list. Wait for the response before applying any fix — a silent contrast substitution changes the brand look in production.

### 8.3 Colour is not the only indicator

Every place where colour alone communicates meaning (red = error, green = success, coloured dot = status) must also carry a text label, icon, or pattern. Screen readers and colour-blind users cannot see colour.
Loading