CSS Tutorial 0/203 lessons ~6 min read Lesson 145

    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

    Course progress0%
    Focus
    27 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    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.
    css
    // ci/a11y-css-audit.mjs
    import { 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):

    css
    # lighthouserc.js
    module.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.

    css
    /* 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

    Preview

    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.
    Ready to mark this lesson complete?Track your journey across the entire course.