prefers-reduced-motion
prefers-reduced-motion
Respect user preference for reduced motion.
CSS Media Query
/* Base animation */
.animated-element {
transition: transform 0.3s ease;
animation: slideIn 0.5s ease;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
.animated-element {
transition: none;
animation: none;
}
}
React Hook
// hooks/useReducedMotion.js
function useReducedMotion() {
const [prefersReduced, setPrefersReduced] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReduced(mediaQuery.matches);
const handler = (e) => setPrefersReduced(e.matches);
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, []);
return prefersReduced;
}
// Usage
function AnimatedComponent() {
const prefersReduced = useReducedMotion();
return (
<div
style={{
transition: prefersReduced ? 'none' : 'transform 0.3s ease',
}}
>
Content
</div>
);
}
Framer Motion Example
import { motion, useReducedMotion } from 'framer-motion';
function AnimatedBox() {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
initial={shouldReduceMotion ? {} : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: shouldReduceMotion ? 0 : 0.3 }}
>
Animated content
</motion.div>
);
}
Implementing Reduced Motion
Implementing Reduced Motion
CSS Transitions
/* Before */
.button {
transition: all 0.3s ease;
}
/* After */
.button {
transition: background-color 0.3s ease, color 0.3s ease;
}
@media (prefers-reduced-motion: reduce) {
.button {
transition: none;
}
}
CSS Animations
@keyframes slideIn {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.animate-slide-in {
animation: slideIn 0.5s ease;
}
@media (prefers-reduced-motion: reduce) {
.animate-slide-in {
animation: none;
opacity: 1;
}
}
JavaScript Animations
function useAnimation() {
const prefersReduced = useReducedMotion();
const animate = useCallback((element, keyframes, options) => {
if (prefersReduced) return;
element.animate(keyframes, {
duration: 300,
easing: 'ease-in-out',
...options,
});
}, [prefersReduced]);
return animate;
}
Scroll Animations
/* Intersection Observer animations */
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s, transform 0.5s;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
.fade-in {
opacity: 1;
transform: none;
transition: none;
}
}
Testing
Testing Reduced Motion
Chrome DevTools
- Open DevTools (F12)
- Open Rendering panel (⋮ → More tools → Rendering)
- Enable "Emulate CSS media feature prefers-reduced-motion: reduce"
System Settings
macOS:
- System Preferences → Accessibility → Display → Reduce motion
Windows:
- Settings → Ease of Access → Display → Show animations
iOS:
- Settings → Accessibility → Motion → Reduce Motion
Android:
- Settings → Accessibility → Remove animations
Testing Checklist
- Animations disabled with reduced motion
- Essential animations still work
- No flashing or blinking
- Transitions instant
- Scroll effects disabled
- Auto-playing videos paused
Essential vs Decorative
// ✅ Essential animation (keep)
function Progress({ value }) {
return (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${value}%`, transition: 'width 0.3s' }}
/>
</div>
);
}
// ❌ Decorative animation (remove)
function Header() {
return (
<div className="header animate-fade-in">
<h1>Title</h1>
</div>
);
}
Practice Problems
Create a reusable React component implementing Reduced Motion. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Reduced Motion using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Reduced Motion for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. What does prefers-reduced-motion: reduce do?
2. Should all animations be removed with reduced motion?
3. What is the primary purpose of Reduced Motion?
4. What is a common mistake when implementing Reduced Motion?
Flashcards
Question
What is prefers-reduced-motion?
Click to reveal answer
Answer
A CSS media query that detects if the user has requested reduced motion.
Question
How do you implement reduced motion?
Click to reveal answer
Answer
Use @media (prefers-reduced-motion: reduce) to disable animations and transitions.
Question
What animations should be kept?
Click to reveal answer
Answer
Essential animations that convey information, like progress bars and loading indicators.
Question
How do you test reduced motion?
Click to reveal answer
Answer
Chrome DevTools Rendering panel, system settings, or browser extensions.
Question
What is Reduced Motion?
Click to reveal answer
Answer
Reduced Motion is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.Always respect prefers-reduced-motion preference
- 2.Disable decorative animations, keep essential ones
- 3.Test with Chrome DevTools and system settings
- 4.Use CSS media query for CSS animations
- 5.Use React hook for JavaScript animations
Interview Tips
- •Explain what prefers-reduced-motion does
- •Discuss which animations to keep vs remove
- •Know how to test reduced motion
Cheat Sheet
Reduced Motion Cheat Sheet
CSS
@media (prefers-reduced-motion: reduce) {
.animated {
animation: none;
transition: none;
}
}
React
const prefersReduced = useReducedMotion();
style={{ transition: prefersReduced ? 'none' : '...' }}
Testing
- Chrome DevTools Rendering panel
- System Accessibility settings
Guidelines
- Remove decorative animations
- Keep essential animations
- No flashing or blinking