**Approach (brief)** Create a CI job that reads design tokens for each theme, renders UI states (Storybook), computes color pair contrasts, and fails when contrast drops below WCAG thresholds or when a token change increases failures vs baseline.**Inputs** - Changed token diff (JSON) from PR or main branch (e.g., tokens.json) - List of themes to test (light/dark/high-contrast) - Storybook build or URL containing components/states to render - Baseline report (stored artifact) for previous contrast pass**Checks the job runs** - Build storybook (or export component screenshots) for each theme (use Storybook + Chromatic or Playwright + Puppeteer) - Extract computed colors per rendered token: read CSS variables or computed styles via Playwright script - Contrast checks: use the color-contrast library / wcag-contrast or axe-core’s color-contrast rules to compute ratios for text/background pairs and UI components - Visual diff guard: run per-component screenshots against baseline with Percy or jest-image-snapshot to detect visual regressions introduced by token changes - Token-level diff check: run a targeted check comparing new token values against baseline; if changed token participates in any failing contrast pair, mark as regression**Tools / libraries** - Storybook (component states) - Playwright or Puppeteer (render + extract computed styles) - axe-core / @axe-core/playwright and wcag-contrast / color-contrast (contrast calculations) - Percy / Chromatic / jest-image-snapshot (visual regression) - Style Dictionary (token management) - Node.js scripts + GitHub Actions / GitLab CI**Example check script (Node + Playwright)** javascript
// node contrast-check.js
const { chromium } = require('playwright');
const { contrast } = require('wcag-contrast');
(async ()=> {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto(process.env.STORYBOOK_URL);
// set theme
await page.evaluate(theme => document.documentElement.setAttribute('data-theme', theme), process.env.THEME);
// get computed colors for target selector
const pairs = await page.evaluate(()=>{
const el = document.querySelector('.text');
const bg = getComputedStyle(el).backgroundColor;
const fg = getComputedStyle(el).color;
return { fg, bg };
});
const ratio = contrast(pairs.fg, pairs.bg);
console.log('contrast', ratio);
if (ratio < 4.5) process.exit(2);
await browser.close();
})();
**Failure reporting** - CI annotates PR with summary: changed tokens, themes impacted, component list, contrast ratios (threshold vs actual). Use GitHub Checks API to post annotations on affected lines/files. - Attach artifacts: per-theme HTML/JSON of failing pairs, screenshots, and a link to Storybook/visual diffs (Percy/Chromatic). - Notify designers & devs: post concise message to PR and Slack channel #design-system with: token name, before/after color swatches, contrast ratio, WCAG level, affected components/screenshots, and suggested fixes (e.g., darken token by X%). Include call-to-action: "Approve token change" or "Update token."**Why this works** - Combines programmatic contrast checks (precise, fast) with visual regression (context-aware) so designers see both numeric violations and visual impact. Automating token-diff focus reduces noise and helps designers/developers quickly triage regressions.