Install
openclaw skills install @raunakkathuria/web-accessibilityApply WCAG 2 Level AA accessibility standards to design systems, UI components, and web pages — contrast math with a runnable validator, design-token patterns that make color accessible by construction, typography and focus-state rules, and a semantics checklist. Use when building or reviewing UI, choosing colors or type for a brand/design system, setting up design tokens, wiring accessibility checks into a build, or when the user mentions accessibility, a11y, WCAG, contrast, or screen readers.
openclaw skills install @raunakkathuria/web-accessibilityMake interfaces conform to WCAG 2 Level AA by building accessibility into the design system, not auditing it in afterwards. A color, text style, or component that enters the system already passing AA scales to every page that uses it; one that doesn't becomes a bug on every page at once.
Conformance target: WCAG 2.2 Level AA (quick reference).
Fixing a failing token fixes every component; fixing a failing page fixes one page. Work top-down.
Never eyeball contrast. The thresholds (WCAG 2.2):
| What renders | Minimum | Success criterion |
|---|---|---|
| Normal text (below 24px / below 18.66px bold) | 4.5:1 | 1.4.3 |
| Large text (≥24px, or ≥18.66px bold) | 3:1 | 1.4.3 |
| Meaningful non-text (icons, chart marks, input borders, focus indicators) | 3:1 | 1.4.11 |
| Disabled controls, decorative elements, logos | exempt | — |
Run the bundled validator (no dependencies, Node ≥14):
# ad-hoc pairs: fg,bg,minimum,label
node scripts/check-contrast.mjs --pair "#0f766e,#ffffff,4.5,link on white"
# a whole palette from a JSON config (see script header for the format)
node scripts/check-contrast.mjs contrast-pairs.json
Wire it into the build. A contrast check that runs once is an audit; one that runs in
npm run build or CI and exits non-zero is a guarantee. Add every token pair that renders as text
or meaningful graphics, in every theme.
Three patterns cover most real-world palette failures:
Split fill roles from text roles. Vibrant brand colors (a teal, an orange, a mid-blue) usually pass as a fill behind dark text but fail as text on a light background — often by a factor of two. Define both and never let components cross them:
--color-primary: #14b8a6; /* fills, borders, focus rings — never text on light */
--color-primary-text: #0f766e; /* the same hue, dark enough to be text (≥4.5:1) */
Filled buttons then choose text per theme: dark text on a bright fill, or light text on a dark fill — whichever pair passes, verified by the script, not by taste.
Theme-tune semantic colors. Success/warning/error values that read well on near-black
(#f59e0b amber, #ef4444 red) fail on white — amber is ~2.2:1 there. Map
--color-success/warning/error per theme instead of sharing one set; keep the bright ramp for
dark themes and a darker ramp (e.g. #b45309, #dc2626) for light.
Subtle text has a floor. Placeholder, hint, and caption text is still text — 4.5:1 applies.
Mid-grays like #9ca3af on white (~2.5:1) are the single most common violation on the web. The
lightest AA gray on white is around #767676; pick your "subtle" token at or below that
lightness.
rem/em) for font sizes, line heights, and containers that hold text —
browser zoom and text-resize must work to 200% without loss of content (SC 1.4.4).html { font-size: 62.5% }
tricks that bake in pixel assumptions.:focus-visible on every interactive element — a 2px+ outline with 3:1 contrast against the
surrounding surface (SC 2.4.7, 2.4.13). Never outline: none without a replacement.prefers-reduced-motion — gate non-essential animation behind the media query.Run through this per component/page; details and fix patterns in references/wcag-checklist.md:
alt text that says what the image does here; alt="" for decorative (1.1.1)<h1>, headings in order, no skipping levels for styling (1.3.1)<button>, <a href>, <label for> — not styled <div>s (4.1.2)aria-current="page", aria-expanded, …)lang attribute and a descriptive <title> (3.1.1, 2.4.2)