Skip to content

feat(a11y): WCAG-compliant focus contrast, 44px targets, reduced motion - #1451

Open
Sundriveauto wants to merge 1 commit into
solutions-plug:mainfrom
Sundriveauto:feat/a11y-wcag-focus-targets-motion
Open

feat(a11y): WCAG-compliant focus contrast, 44px targets, reduced motion#1451
Sundriveauto wants to merge 1 commit into
solutions-plug:mainfrom
Sundriveauto:feat/a11y-wcag-focus-targets-motion

Conversation

@Sundriveauto

@Sundriveauto Sundriveauto commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Closes #1320
Closes #1321
Closes #1322
Closes #1323

Summary

Closes the WCAG 2.1 AA compliance requirement behind the frontend's "WCAG 2.1 AA" tagline and its dedicated accessibility style suite. Three gaps are closed so the existing accessibility.yml CI workflow truly enforces the standard:

  1. :focus-visible outlines now meet non-text-contrast (≥ 3:1) against every background token in both color schemes.
  2. Minimum 44 × 44 CSS px interactive target size (WCAG 2.5.5) for buttons, links, and form controls.
  3. prefers-reduced-motion disables non-essential animation (spinners, skeleton shimmer, toast transitions).

Problem

  • The old focus ring used var(--ring) (gold #f59e0b). On light background tokens it only reached ~1.9–2.2:1 — below the 3:1 WCAG non-text-contrast threshold. Gold is fine on dark surfaces but fails on light ones.
  • The interaction-size rule only set min-height: 44px on button/a[role='menuitem']/checkbox/radio/select — plain links and full 44 × 44 targets were not enforced.
  • The reduced-motion block existed but wasn't verified, and prefers-reduced-motion coverage of spinners/shimmer/toasts wasn't backed by tests.

Changes

Focus indicator contrast (tokens.css, accessibility.css)

  • Added a mode-aware --ring-strong token:
    • Dark: #fbbf24 (verified 9.5–11.8:1 on all dark bg tokens)
    • Light: #1e3a8a (verified 9.2–10.4:1 on all light bg tokens)
  • *:focus-visible and .skip-link:focus now use var(--ring-strong, var(--ring, #f59e0b)) (graceful fallback).

Interactive target size (accessibility.css)

  • Replaced the partial rule with a full target covering button, [role='button'], a[href], input, select, textarea, summary at min-width: 44px; min-height: 44px.

Reduced motion (accessibility.css)

  • Global kill of animation/transition under prefers-reduced-motion: reduce, plus an explicit animation: none for .spinner, .loading-spinner .spinner, and .skeleton (shimmer). Toast transitions are covered by the universal transition-duration: 0.01ms.

Test suite (accessibility.css.test.ts)

  • Expanded from a single .visually-hidden uniqueness check to 9 tests guarding all three requirements:
    • Per-scheme ≥ 3:1 contrast of --ring-strong against every background token (including composited --surface-glass)
    • Presence of 44 × 44 rules and the full selector list
    • The @media (prefers-reduced-motion: reduce) query and spinner/shimmer/toast disablement

Tooling (package.json)

  • Fixed test:a11y from Jest-30-unsupported --testPathPattern--testPathPatterns so the accessibility.yml jest-axe-tests job actually runs the suite (it was a no-op/broken flag).

Verification

  • cd frontend && npx jest src/styles/__tests__/accessibility.css.test.ts9/9 passing
  • Contrast math confirmed with the WCAG relative-luminance formula for every opaque background token in each scheme; the previously-failing gold-on-light case (≈2:1) is now ≥ 9:1.

Testing checklist

  • accessibility.css.test.ts passes (9/9)
  • test:a11y script executes the suite under Jest 30 (was broken before)
  • Component suite (LandingPage.accessibility.test.tsx) — note: 6 pre-existing failures unrelated to this change (form-validation error text / aria-describedby wiring); out of scope here.

Notes / follow-ups

  • No breaking changes; tokens are additive and all existing --ring usages keep their fallback.
  • The unrelated, pre-existing failures in LandingPage.accessibility.test.tsx are tracked as a separate concern from this PR.

Files modified

  • frontend/src/styles/tokens.css — add --ring-strong (dark + light)
  • frontend/src/styles/accessibility.css — focus ring, 44×44 targets, reduced motion
  • frontend/src/styles/__tests__/accessibility.css.test.ts — expanded suite
  • frontend/package.jsontest:a11y Jest flag

Recreate :focus-visible outlines with >= 3:1 non-text contrast against
every background token by adding a mode-aware --ring-strong token: the
previous gold ring only reached ~2:1 on light surfaces. Enforce a 44x44
minimum interactive target for buttons/links/form controls (WCAG 2.5.5)
in place of the partial min-height-only rule, and strengthen the
prefers-reduced-motion block so spinners, skeleton shimmer, and toast
transitions are disabled.

Expand the accessibility.css test suite to guard all three behaviours
per scheme, and fix the test:a11y script's now-unsupported Jest flag
(--testPathPattern -> --testPathPatterns) so accessibility.yml CI runs
the suite.

Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
@drips-wave

drips-wave Bot commented Aug 29, 2026

Copy link
Copy Markdown

@Sundriveauto Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant