fix: honor prefers-reduced-motion for page transitions (MON-194) - #297
Conversation
The landing page's cinematic scroll hero already gated on useReducedMotion(). The remaining gap was PageTransition.tsx, which runs an unguarded fade/translate on every route change, and the globals.css nudge keyframe having no native CSS fallback. Guard the page-transition animation behind useReducedMotion() and add a global @media (prefers-reduced-motion: reduce) override in globals.css as a CSS-level fallback for anything not already gated in script. Remove the now-fixed item from the /accessibilite declaration.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🧪 dbt data-health check — ✅ passedValidates prod data health. A failure means current prod data is stale/broken, not necessarily that this PR is wrong. |
Walid-peach
left a comment
There was a problem hiding this comment.
Attention Score
100/100 - SAFE TO SKIM
- Base 100
- No Must Fix findings
- No Should Fix findings
- Verdict is not "Needs changes" or "Scope should be split"
- No high-risk paths touched (no migrations, workflows, railway.json, api/main.py CORS/rate-limit, requirements*.txt)
- Diff is small: 21 additions / 6 deletions across 3 files (well under 400 lines / 15 files)
- No new
api/,rag/, orscripts/code, so the "no test changes" penalty doesn't apply - Testing / Validation Gaps section is non-empty (see below) but doesn't cross a threshold worth penalizing given how mechanical the change is and how well-established the pattern already is elsewhere in the codebase
- Nothing the agent couldn't verify from the diff/CI: build, lint, and full Jest suite already pass in CI
Reason you can skim: three small, mechanical, well-precedented changes with all CI green and no risky surface touched.
Summary
This PR closes the RGAA 13.8/13.9 / WCAG 2.3.3 gap for prefers-reduced-motion. It correctly identifies that the audit's headline claim was stale — AssemblyScrollExperience.tsx already gates its cinematic scroll hero behind useReducedMotion() — and instead fixes the two real remaining gaps: PageTransition.tsx (an unguarded fade+translate on every route change) and a missing CSS-level @media (prefers-reduced-motion: reduce) fallback in globals.css. It also removes the now-resolved item from /accessibilite, following the exact pattern of the three prior RGAA-fix PRs (MON-192/193/195).
Must Fix
None.
Should Fix
None.
Nice to Have
PageTransition.tsxhas no test coverage for thereduceMotionbranch.QuizClient.test.tsxalready shows the pattern for mockinguseReducedMotionfromframer-motionin this codebase, so a similar test here (assertingopacity/transformare set immediately and therequestAnimationFramepath is skipped) would be cheap to add and would guard the behavior long-term. Not blocking given how small and mechanical the change is.- The global CSS override in
globals.cssuses a broad*, *::before, *::afterselector. This is the standard, well-known idiom for this problem (and intentionally uses0.001msrather than0ssotransitionendlisteners still fire), so it's not a concern — just worth a reviewer's awareness that it also collapses the very short hover micro-transitions inHemicycleChart.tsx(0.1–0.15s) under reduced motion, which is expected/desired but slightly broader than "large motion" framing in the issue title.
Testing / Validation Gaps
- No new automated test was added for the
PageTransitionreduced-motion branch (see Nice to Have above). - No manual browser verification with OS-level "reduced motion" toggled was performed (per the PR's own Testing section) — verified by code reading only. Low risk given the identical pattern already ships and works for
AssemblyScrollExperience,LiveAssemblyPulse, andQuizDeck.
Documentation / Reviewer Notes
/accessibiliteupdate correctly follows the established pattern (MON-192/193/195) for removing a fixed non-conformity item — no further doc changes needed.- Reviewers should double check in a real browser with "reduce motion" enabled that page navigation no longer shows the fade/translate, since that's the one path not verified end-to-end here.
Verdict
Ready to merge
MON-196 (deputy photo alt text) merged in parallel and also removed a non-conformity item from the same /accessibilite list. With both items now fixed, rewrite the "Résultats des tests" and "Contenus non accessibles" sections to state no known non-conformities remain, instead of leaving an orphaned empty list.
What
Closes the remaining gap in
prefers-reduced-motionsupport flagged by the RGAA self-audit (MON-194, RGAA 13.8/13.9 / WCAG 2.3.3), and removes the now-fixed item from the/accessibilitedeclaration.Why
The audit found
prefers-reduced-motionunreferenced by literal grep, but the landing page's cinematic scroll hero (AssemblyScrollExperience.tsx) already gates on framer-motion'suseReducedMotion()and falls back toStaticExperience— that's why the string search came up empty even though the behavior existed. Auditing the actual remaining continuous/large-motion effects turned up two real gaps:PageTransition.tsx, an unguarded fade + 6px translate that runs on every route change site-wide, and no CSS-levelprefers-reduced-motionfallback inglobals.cssfor anything not already gated in script (e.g. thenudgescroll-hint keyframe).Changes
frontend/src/components/PageTransition.tsx: readuseReducedMotion()and skip the fade/translate animation when set, showing content immediately instead.frontend/src/app/globals.css: add a global@media (prefers-reduced-motion: reduce)override that collapses animation/transition durations and disables smooth scrolling, as a CSS-level fallback in addition to the existing JS gates (AssemblyScrollExperience,LiveAssemblyPulse,QuizDeck, and nowPageTransition).frontend/src/app/accessibilite/page.tsx: remove the "Animations non désactivables" non-conformity item now that it's fixed, following the same pattern as the MON-192/193/195 fixes.Testing
npm run lint— clean.npm test— 25 suites / 197 tests pass.npm run build— compiles and type-checks cleanly; the/votesprerender step fails locally with a 500 from the API, but that's a pre-existing local-environment gap (no local API/DB running against which to statically generate that page) — confirmed the change only touchesPageTransition.tsx,globals.css, and the static/accessibilitepage, none of which touch/votesdata fetching.useReducedMotion()from framer-motion reflectsmatchMedia('(prefers-reduced-motion: reduce)')synchronously on first client render, soAssemblyScrollExperiencerendersStaticExperienceandPageTransitionskips its transition, for a user with the OS preference set.Risks / Notes
globals.cssis a broad*selector; it only fires underprefers-reduced-motion: reduceand only clamps animation/transition duration, so it shouldn't visually break anything — it just makes the very short hover micro-transitions (e.g.HemicycleChartseat dots) effectively instant too, which is expected and desired.Breaking Changes
None.