--- name: design-qa description: Use when a UI screen, page or component is about to be called done, handed off or shipped, or when generated UI looks generic or half-finished. Gates the rendered result, not the design file - finds controls that do nothing, placeholder copy, removed focus rings, missing loading/empty/error states, low contrast, small targets, hidden focus and broken reflow measured in the real browser, and flags template defaults without blocking on taste. argument-hint: "[UI source path | state matrix CSV | rendered snapshot JSON | URL to capture]" license: MIT metadata: version: 1.0.0 build_pattern: "Three deterministic stdlib scripts (source finish lint, interaction-state matrix, rendered snapshot audit) with blocking exit codes, plus a read-only browser collector and an optional Playwright keyboard pass; WCAG 2.2 AA thresholds for contrast, focus, reflow, names and target size; template traits from Anthropic's frontend-design skill as warnings only" distinct_from: "a11y-audit (source-level WCAG scan: alt text, labels, ARIA, headings); ui-design-system (generates tokens and components); ux-researcher-designer (research); epic-design and frontend-design (make the design, this checks the build)" --- # Design QA > **Portability:** 3 stdlib Python scripts, no network. The browser collector is > plain JavaScript; the keyboard pass needs Playwright in your own project. > **This checks the build, not the taste.** Objective defects block; template > defaults only warn, because the brief may ask for them. Screens get reviewed in a static, populated, happy-path frame and ship with a button wired to `() => {}`, an empty state that says "Lorem ipsum", and a focus ring someone removed. Each script catches one class of these before a user does. ## Workflow ```bash # 1 - Source: is anything unfinished in the code? python scripts/finish_lint.py src/ --output json > lint.json # 2 - States: does every surface have loading, empty, error, busy, focus...? python scripts/state_matrix_checker.py --matrix states.csv --evidence-root . # 3 - Rendered: what did the browser actually show? (capture 320 px and desktop) node assets/capture_snapshot.mjs http://localhost:3000/invoices --width 320 --out s320.json python scripts/rendered_audit.py --snapshot s320.json --waivers waivers.csv ``` **Step 1** blocks on inert handlers, dead links, placeholder copy and focus outlines removed without a replacement. It warns on sample data, token drift and each template trait it finds, and rates generic risk LOW, MEDIUM or HIGH. **Step 2** derives required states from each surface's kind (`data_view`, `form`, `action`, `control`, `media`, `page`). An unlisted state counts as missing, and DONE without evidence is reported. Start from `assets/states.example.csv`. **Step 3** reads a snapshot of the rendered page and blocks on WCAG 2.2 AA failures: text contrast, no focus indicator, focus obscured, reflow at 320 px, empty accessible names and targets under 24×24 px without the spacing exception. Contrast is composited through translucent layers and `opacity`, so a token that passes on white but fails inside a tinted card is caught. Repeated rows are grouped. No Playwright? Paste `assets/collect_snapshot.js` into the DevTools console and run `copy(JSON.stringify(designQaSnapshot()))`. Focus results are then unverified rather than passed. Capture one snapshot per key state (empty, error, long content), not just the populated page. Fill `assets/finish_signoff.md` from the three runs. ## Hard rules - **Unlisted is missing.** A state nobody wrote down was not built. - **Measure the render, not the CSS.** Contrast on a real background beats a declared colour pair. - **Never block on taste.** Template traits warn. Record in the sign-off whether the brief asks for the look. - **A waiver names the WCAG exception or a ticket.** The scripts reject a waiver or NA with no reason. - **Unverified is not pass.** Text on a gradient or focus from script focus is checked by hand or with the keyboard pass. - **Not an accessibility conformance claim.** The checks cover what can be measured. A screen-reader pass and `a11y-audit` cover the rest. ## Scripts | Script | Role | Exit codes | |---|---|---| | `scripts/finish_lint.py` | Inert controls, dead links, placeholder copy, removed focus, template traits, token drift | 0 clean · 2 findings · 3 bad input · 4 blocked | | `scripts/state_matrix_checker.py` | Required states per surface kind, evidence and NA reasons | 0 complete · 2 · 3 · 4 blocked | | `scripts/rendered_audit.py` | Contrast, focus, target size, names, clipping, reflow from a snapshot | 0 clean · 2 · 3 · 4 blocked | All support `--help`, `--sample` and `--output json`. ## References and assets - [`references/rendered_checks_wcag22.md`](references/rendered_checks_wcag22.md): each rendered check, its WCAG 2.2 criterion and exceptions (7 sources) - [`references/interaction_states.md`](references/interaction_states.md): required states per kind and what done looks like (7 sources) - [`references/generic_ui_defaults.md`](references/generic_ui_defaults.md): unfinished-work patterns and the template traits (6 sources) - [`assets/collect_snapshot.js`](assets/collect_snapshot.js), [`assets/capture_snapshot.mjs`](assets/capture_snapshot.mjs): snapshot capture - [`assets/states.example.csv`](assets/states.example.csv), [`assets/snapshot.example.json`](assets/snapshot.example.json), [`assets/waivers.example.csv`](assets/waivers.example.csv): input formats - [`assets/finish_signoff.md`](assets/finish_signoff.md): sign-off before a screen is called done