Some users need reduced or no motion for medical reasons.
Why It Matters
- Vestibular disorders affect ~35% of adults over 40
- Motion can cause dizziness, nausea, headaches
- Some users have motion sensitivity
- Required for WCAG 2.1 Level AA compliance
prefers-reduced-motion Media Query
css/* Default animation */ .animated { animation: slide-in 300ms ease; } /* Reduce or remove for those who prefer */ @media (prefers-reduced-motion: reduce) { .animated { animation: none; /* Or use a simpler animation */ animation: fade-in 150ms ease; } }
React Hook for Reduced Motion
jsxfunction useReducedMotion() { const [prefersReducedMotion, setPrefersReducedMotion] = useState(false); useEffect(() => { const mediaQuery = window.matchMedia( '(prefers-reduced-motion: reduce)' ); setPrefersReducedMotion(mediaQuery.matches); const handler = (e) => setPrefersReducedMotion(e.matches); mediaQuery.addEventListener('change', handler); return () => mediaQuery.removeEventListener('change', handler); }, []); return prefersReducedMotion; } // Usage function AnimatedComponent() { const prefersReducedMotion = useReducedMotion(); return ( <div style={{ transition: prefersReducedMotion ? 'none' : 'transform 300ms ease', }} > Content </div> ); }
Framer Motion's useReducedMotion
jsximport { motion, useReducedMotion } from 'framer-motion'; function Card() { const shouldReduceMotion = useReducedMotion(); return ( <motion.div initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: shouldReduceMotion ? 0.1 : 0.3, }} > Card content </motion.div> ); }
MotionConfig for Global Settings
jsximport { MotionConfig } from 'framer-motion'; function App() { return ( <MotionConfig reducedMotion="user"> {/* All motion components respect user preference */} <Main /> </MotionConfig> ); } // Options: // "user" - respect system preference (default) // "always" - always reduce motion // "never" - ignore preference
Safe vs Problematic Animations
jsx// ✅ Safe animations (usually okay to keep) // - Opacity fades // - Color changes // - Small scale changes // - Border/shadow changes // ⚠️ Potentially problematic (reduce or remove) // - Large translations // - Rotations // - Parallax scrolling // - Flashing/blinking // - Zooming // - Complex sequences
Implementation Pattern
jsxconst animationVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 }, }; const reducedMotionVariants = { hidden: { opacity: 0 }, visible: { opacity: 1 }, }; function AdaptiveAnimation({ children }) { const shouldReduceMotion = useReducedMotion(); const variants = shouldReduceMotion ? reducedMotionVariants : animationVariants; return ( <motion.div variants={variants} initial="hidden" animate="visible" transition={{ duration: shouldReduceMotion ? 0.1 : 0.3 }} > {children} </motion.div> ); }