import { useEffect, useState } from 'react'; const REDUCED_MOTION_QUERY = '(prefers-reduced-motion: reduce)'; const readReducedMotion = (): boolean => typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia(REDUCED_MOTION_QUERY).matches; /** * Reactively tracks the OS `prefers-reduced-motion: reduce` setting. * * Unlike a one-off `window.matchMedia(...).matches` read, this subscribes to the * media query's `change` event, so toggling the OS setting mid-session updates * the returned value (and any animation gated on it) without a page reload. * SSR/undefined-safe: returns `false` when `window`/`matchMedia` is unavailable. */ export function useReducedMotion(): boolean { const [reduced, setReduced] = useState(readReducedMotion); useEffect(() => { if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') { return undefined; } const mql = window.matchMedia(REDUCED_MOTION_QUERY); const onChange = (event: MediaQueryListEvent) => setReduced(event.matches); // Re-sync in case the setting changed between the initial render and this effect. setReduced(mql.matches); mql.addEventListener('change', onChange); return () => mql.removeEventListener('change', onChange); }, []); return reduced; }