From e3d72ab0e7460e2103136d3fc76ede5d2a5a8706 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Tue, 29 Sep 2026 13:26:54 +0200 Subject: [PATCH] fix(web): make motion animations instant when reduced motion is on Motion's reducedMotion="user" only drops transforms, so opacity fades still ran and could be caught half-faded (axe flagged the assistant). The design makes every duration instant under reduced motion, as the CSS tokens already do. --- apps/web/src/libs/motion.ts | 14 ++++++++++++++ apps/web/src/main.tsx | 3 +++ 2 files changed, 17 insertions(+) diff --git a/apps/web/src/libs/motion.ts b/apps/web/src/libs/motion.ts index dc518dc9c..661cd23d5 100644 --- a/apps/web/src/libs/motion.ts +++ b/apps/web/src/libs/motion.ts @@ -1,4 +1,5 @@ import type { CSSProperties } from "react"; +import { MotionGlobalConfig } from "motion/react"; // Motion (JS) mirrors of the CSS motion tokens in packages/ui/src/styles/globals.css (--d1/--d2/--d3, --ease, // --ease-in-out-strong). Durations are in seconds, Motion's unit; multiply by 1000 for APIs that take ms (dnd-kit). @@ -45,3 +46,16 @@ export const stagger = (index: number) => ({ "--stagger": `${Math.min(index, 5) /** A status icon arriving in place (a success check, a spinner taking its slot): fades and scales up from 90% over D1. */ export const POP_CLASS = "starting:scale-90 starting:opacity-0 transition-[opacity,scale] duration-quick ease-enter"; + +/** + * Reduced motion makes every duration instant (DESIGN.md, Motion). The CSS tokens drop to 1ms; Motion's own + * `reducedMotion="user"` only drops transforms and still fades, so its animations are made instant here as well. + */ +export function followReducedMotion() { + const query = window.matchMedia("(prefers-reduced-motion: reduce)"); + const apply = () => { + MotionGlobalConfig.instantAnimations = query.matches; + }; + apply(); + query.addEventListener("change", apply); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index de9ad48d2..76f581d55 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,11 +1,14 @@ import { RouterProvider } from "@tanstack/react-router"; import ReactDOM from "react-dom/client"; +import { followReducedMotion } from "./libs/motion"; import { getRouter } from "./router"; import "./index.css"; const rootElement = document.getElementById("app"); if (!rootElement) throw new Error("Root element not found"); +followReducedMotion(); + const router = await getRouter(); // Server metadata describes the initial URL. The SPA router owns these tags after