import { useEffect, useState } from "react"; /** * Returns true when the user has expressed a preference for reduced motion * (OS-level setting: `prefers-reduced-motion: reduce`). * * SSR-safe: the lazy initializer reads the live matchMedia value on the client * (and falls back to `false` when `window`/`matchMedia` is unavailable, e.g. * during SSR), so a reduced-motion user does NOT get one frame of animation * before the effect reconciles. The effect then only listens for changes. */ export default function useReducedMotion(): boolean { const [reduced, setReduced] = useState(() => { if (typeof window === "undefined" || !window.matchMedia) return false; return window.matchMedia("(prefers-reduced-motion: reduce)").matches; }); useEffect(() => { if (typeof window === "undefined" || !window.matchMedia) return; const mq = window.matchMedia("(prefers-reduced-motion: reduce)"); // Reconcile in case the value changed between the lazy init and mount // (or after SSR hydration). setReduced(mq.matches); const onChange = () => setReduced(mq.matches); mq.addEventListener("change", onChange); return () => mq.removeEventListener("change", onChange); }, []); return reduced; }