Accessibility Audits
accessibility audits css accessibility audits combine automated scanners (axe-core, lighthouse, pa11y), design-token linting, and manual keyboard/screen-reader review to catch visual
Introduction
CSS accessibility audits combine automated scanners (axe-core, Lighthouse, pa11y), design-token linting, and manual keyboard/screen-reader review to catch visual WCAG failures before production. Staff engineers define audit scope per route and component — not a once-a-year PDF from a vendor.
Business problem
Point-in-time audits without CI regression gates fail within one sprint — new CSS overrides reintroduce outline removal and contrast failures. Legal and procurement require continuous evidence, not stale VPATs.
- Cost: External audit remediation runs $200k+ when CSS debt is deep.
- Velocity: PR-level axe gates shift-left — fix in minutes not months.
- Coverage: Storybook-only scans miss hydration and route-specific CSS.
Why this feature exists
Automated tools detect ~30–50% of WCAG issues — mostly CSS/HTML patterns with deterministic rules. ACT rules standardize what axe and Lighthouse test. Manual audit fills gaps: focus order, SR announcement quality, cognitive load.
- axe-core: De facto engine for color-contrast, aria, focusable content rules.
- Lighthouse: Accessibility category uses axe + additional heuristics; scores are useful for trends not legal sign-off alone.
- VPAT/ACR: Requires documented test method — automated + manual.
Browser rendering perspective
Auditors must test rendered CSS — computed styles determine contrast ratios, visibility, and size. Source SCSS may pass lint but compiled CSS with !important overrides fails axe on production build.
- Computed style: axe reads getComputedStyle for contrast — not author stylesheet alone.
- Shadow DOM: Web component CSS must be included in scan scope.
- Lazy routes: SPA pages need navigation before scan — empty shell passes falsely.
Internal browser workflow
Audit pipeline: PR preview deploy → axe-playwright on changed routes → Lighthouse CI budget → stylelint-a11y on changed CSS → quarterly manual WCAG walkthrough with certified tester.
- Baseline: Store axe violation JSON per route — diff on PR shows regressions only.
- Exceptions: Document known false positives with ticket + expiry date.
- CSS lint: Ban outline:none, require focus-visible plugin rules.
Feature deep dive
Three-layer CSS audit stack:
- Token lint: Contrast matrix validation at build time (APCA optional for future).
- Component scan: axe on Storybook stories × themes × viewports.
- Integration scan: Playwright navigates real flows — checkout, auth, settings.
// ci/a11y-css-audit.mjsimport { AxeBuilder } from "@axe-core/playwright";const results = await new AxeBuilder({ page }).withTags(["wcag2aa", "wcag22aa"]).include(".app-root").analyze();const cssRelated = results.violations.filter(v =>["color-contrast", "target-size", "focus-order"].includes(v.id));if (cssRelated.some(v => v.impact === "critical")) process.exit(1);
Syntax
Lighthouse CI accessibility gate (css-relevant rules):
# lighthouserc.jsmodule.exports = {ci: {assert: {assertions: {"categories:accessibility": ["error", { minScore: 0.95 }],"color-contrast": "error","target-size": "warn",},},},};
Examples
PR comment bot posts axe color-contrast failures with selector and computed colors — engineer fixes token not one-off hex.
/* Before audit fail: .muted { color: #999; background: #fff; } — 2.8:1 *//* After: .muted { color: var(--text-secondary); } — token enforces 4.5:1 */
Real-world use
Netflix, Microsoft, and Shopify run axe in CI on design system and critical flows. UK GDS publishes manual + automated test approach. Deque, Level Access, and Paciello Group sell audits — engineering teams still own continuous regression.
Real production example
Monorepo pattern: Turbo pipeline runs axe on affected apps' changed routes only; design-system package blocks publish on any story axe critical.
- Artifact: Upload axe JSON to S3 per build — compliance queries by git SHA.
- Lighthouse: Run on 3 viewports — mobile CSS breakpoints hide different contrast failures.
Enterprise use case
Enterprise maintains accessibility steering committee — audit KPIs: critical violations trend, manual test hours per quarter, % components with signed WCAG checklist.
- Vendor: Annual third-party audit validates internal CI — not replaces it.
- Procurement: ACR updated each major release with test scope appendix.
Accessibility considerations
Audit scope must include CSS states: hover, focus, focus-visible, disabled, error, loading — axe scans default state only unless stories exercise each.
- False negatives: Custom focus ring via opacity:0 until :focus-visible — verify manually.
Performance considerations
Full-site axe on large SPAs is slow — scope by sitemap priority and changed-file graph. Lighthouse accessibility pass adds ~30s per URL — parallelize in CI matrix.
- Cache: Reuse browser context; snapshot CSSOM only when styles change.
SEO considerations
Lighthouse accessibility score correlates with mobile usability — audit failures on tap targets affect both a11y and SEO hygiene reports.
Scalability considerations
Micro-frontends: Each MFE owns axe gate on its routes; shell app runs integration scan for focus order across MFE boundaries — CSS z-index wars break focus obscured checks.
- White-label: Per-tenant theme audit matrix — N tenants × M routes combinatorial; prioritize revenue paths.
Common production issues
Failures: Team celebrated Lighthouse 100 on homepage while checkout used hardcoded #767676 on #777 — unrouted scan. stylelint disabled in legacy package — outline:none returned via hotfix.
- Lesson: Define critical path URL list mandatory for every release candidate.
Debugging guide
When axe reports color-contrast: DevTools → inspect → Accessibility pane shows ratio; check pseudo-elements and background-image text overlays.
- Flaky: Animation mid-transition during scan — pause animations or scan settled state.
- iframe: axe.include() each payment iframe separately.
Best practices
- Run axe on production build CSS, not dev HMR styles.
- Gate PR on zero new critical violations vs baseline diff.
- Include focus-visible and error states in Storybook a11y stories.
- Pair Lighthouse trends with axe JSON for criterion-level detail.
- Quarterly manual audit on top of daily automation.
Anti-patterns
- Relying on Lighthouse 100 as legal WCAG sign-off.
- Disabling axe rules globally instead of fixing root token.
- Auditing Storybook only — missing page-level CSS overrides.
- One-time vendor audit without CI regression.
Trade-offs
- Benefit: Continuous audits catch CSS regressions at PR time.
- Benefit: axe criterion IDs map directly to engineer tasks.
- Cost: CI time and flake management on visual/async CSS.
- Cost: Manual audit still required for legal defensibility.
- Risk: Over-trusting automation misses 50%+ of real user issues.
Architecture review questions
- Is axe running on rendered PR previews for changed routes?
- Are CSS-related rules (contrast, target-size) in assert failure set?
- Do audits cover dark mode and high-contrast themes?
- Is there a manual keyboard/SR schedule beyond automation?
- Are audit artifacts retained per release for compliance?
Interview questions
How do you structure CSS accessibility audits in CI?(Advanced)
Layer 1: token contrast lint at build. Layer 2: axe on Storybook components all themes. Layer 3: Playwright axe on changed production routes. Layer 4: Lighthouse CI budget. Diff violations vs baseline; block critical. Quarterly manual WCAG test.
Follow-up: Lighthouse vs axe for CSS?
What do automated tools miss for CSS a11y?(Advanced)
Focus order vs visual order from flex/grid reorder, quality of focus indicator (visible but low contrast custom ring), sticky obscuring focus, misleading visual hierarchy, cognitive load, SR announcement timing — all need manual keyboard and SR testing.
Follow-up: How handle known false positives?
Hands-on exercise
Exercise: Add axe-playwright to a sample app; intentionally break contrast; fix via token; configure Lighthouse CI min accessibility 0.95 on two routes.
- Store axe JSON artifact in CI output.
- Document one manual test not covered by axe.
Staff engineer notes
- Audit the CSS that ships — computed styles on production build.
- Baseline diff > absolute zero — legacy debt exists; prevent new violations.
- Lighthouse is trend; axe violations are tickets.
Common pitfalls
- Scanning before web fonts load — false contrast passes/fails.
- Ignoring CSS inside shadow roots of third-party widgets.
Try it yourself
Edit the CSS panel — the preview updates live. Use DevTools Performance and Accessibility panels to validate.
Try it yourself
Summary
CSS accessibility audits combine axe-core, Lighthouse, style linting, and manual testing in CI — scoped to rendered production styles, critical user flows, and all theme variants — with artifacts retained for compliance evidence.
Key takeaways
- Automate CSS a11y with axe + Lighthouse CI on real routes and production CSS.
- Manual keyboard/SR testing remains mandatory for legal-grade conformance.
- Token-level contrast lint prevents the most common audit failures.