Born-Accessible UI
Build visually rich UI that is accessible on the first pass. Run this loop for every UI change:
Plan → Build → Verify → Fix → Report
- Full rules (read them before your first UI change in a session): references/protocols.md
- How to fix audit findings: references/remediation.md
- In the commands below,
<skill>is the folder that contains this SKILL.md file.
1. Plan (Protocol 1: Silent Reasoning)
Before writing markup, write an A11Y-PLAN that covers:
- Landmarks: what goes into
<header>,<nav aria-label>,<main>,<aside aria-label>and<footer>. - Headings: one
<h1>per page, thenh2andh3without skipping levels. - Controls: every interactive element, its native HTML element, its accessible name and its keyboard behavior.
- Contrast pairs: every text/background and UI/background color pair with its computed ratio.
Put the plan where it can't break the build: an HTML comment after <!DOCTYPE html> in a standalone page, a {/* A11Y-PLAN ... */} block in JSX/TSX, an HTML comment in Vue/Svelte/Angular templates, or your reply when you change several files.
When you build a single component, plan only its own semantics. Landmarks and the <h1> belong to the page layout, not to every component.
2. Build (Protocols 2 and 3)
- Color tokens: define colors as custom properties and annotate every foreground token with its computed contrast ratio, rounded down:
--text-muted: #cbd5e1; /* Contrast: 11.9:1 vs #111827 (AAA) */. Minimums: 4.5:1 for normal text, 3:1 for large text and for UI parts such as input borders, focus rings and icons. - Native HTML first:
<button>for actions,<a href>for navigation,<dialog>withshowModal()for modals,<details>/<summary>for disclosures,<input type="checkbox" role="switch">for toggles,<label for>for every form control. Never put click handlers on<div>or<span>. - Layout: content reflows at 320 CSS px without horizontal scrolling, targets are at least 24×24 px,
:focus-visibleis clearly visible, and motion respectsprefers-reduced-motion.
3. Verify (never skip, never estimate)
Run both checks after every UI change:
bash# Static: recomputes every "Contrast:" comment, flags clickable <div>s and removed outlines node <skill>/scripts/lint.mjs <changed files or src folder> # Runtime: Playwright + axe-core (WCAG 2.2 A/AA + best practices) on the initial view and on # hidden states (dialogs, popovers, details, tabs, menus), plus a 320px reflow check node <skill>/scripts/audit.mjs <url-or-html-path> [more targets]
- Audit every page or route you changed. For apps, start the dev server in the background first and pass its URLs (for example
http://localhost:5173/settings). For static HTML, pass the files or a folder. - UI that only exists after a click, such as a conditionally rendered modal: add
--click "<CSS selector of the trigger>". - Exit codes:
0= no automated violations,1= violations (details ina11y-report/),2= setup or page-load problem (is the dev server running?). - Missing dependencies: run
npm install --save-dev playwright @axe-core/playwrightin the project. Only if Google Chrome isn't installed, also runnpx playwright install chromium. - Keep
a11y-report/out of version control (add it to.gitignore).
4. Fix
- Read
a11y-report/fix-prompt.md(ora11y-report/report.jsonfor full details) and the lint output. Fix lint errors too: axe can't see clickable<div>s or wrongContrast:comments. To compare against the starting point later, run the first audit with--out a11y-report/before. - Fix at the source (tokens, shared components, layout rules) following references/remediation.md. Keep the visual design: change lightness rather than going monochrome, and never hide content from assistive technology to silence a rule.
- Run lint and audit again. Do at most 3 fix rounds (a round = fixing violations, then re-running both checks). If violations remain, stop and report them with your best explanation.
- Check every "needs review" item (the
reviewlines in the audit output, also listed infix-prompt.mdandreport.json). They are not failures; axe-core couldn't decide. These checks don't count toward the 3 rounds.- Text on gradients, images or translucent ("glass") surfaces: check the worst spot by eye, or put a solid backdrop behind the text so axe can compute it.
- "Partially overlaps other elements", common for text inside a modal
<dialog>or near positioned elements: don't change code for this. Verify the real colors in DevTools or a screenshot and record the result.
5. Report
- Say "0 automated axe-core violations". Never claim "WCAG compliant" or "accessibility verified": automated tools catch only part of all barriers.
- List each "needs review" item you checked by hand as manually verified, with the colors and ratio you measured.
- List what still needs a human: a keyboard walk-through (Tab, Shift+Tab, Enter, Space, Esc, arrow keys in composite widgets), visible focus everywhere, a screen reader spot check of names, roles and states, 200% zoom, and meaningful alt text, link text and error messages.


