useReducedMotion
A Hook for detecting whether the user has enabled the "reduce motion" preference setting.
Basic Usage
Live Editor
function Demo() { const reducedMotion = useReducedMotion(); return ( <div style={{ padding: '24px', backgroundColor: reducedMotion ? 'var(--ifm-color-warning-lightest)' : 'var(--ifm-color-success-lightest)', borderRadius: '8px', textAlign: 'center', }} > <div style={{ fontSize: '48px', marginBottom: '12px' }}> {reducedMotion ? '🐢' : '⚡'} </div> <div style={{ fontSize: '20px', fontWeight: 'bold', marginBottom: '8px' }}> {reducedMotion ? 'Reduced Motion Mode' : 'Normal Animation Mode'} </div> <div style={{ fontSize: '14px', color: 'var(--ifm-color-emphasis-700)' }}> Current user preference: {reducedMotion ? 'Reduce motion' : 'Allow animations'} </div> </div> ); }
Result
Loading...
Conditional Animation
Enable or disable animations based on user preference:
Live Editor
function Demo() { const reducedMotion = useReducedMotion(); const [count, setCount] = useState(0); return ( <div> <Button onClick={() => setCount(count + 1)} style={{ marginBottom: '16px' }}> Click to Increase ({count}) </Button> <div style={{ padding: '40px', backgroundColor: 'var(--ifm-color-primary-lightest)', borderRadius: '8px', textAlign: 'center', transition: reducedMotion ? 'none' : 'all 0.5s ease', transform: `scale(${1 + count * 0.05})`, }} > <div style={{ fontSize: '48px' }}>🎯</div> <div style={{ marginTop: '12px', fontSize: '14px' }}> {reducedMotion ? 'Instant scale' : 'Smooth scale animation'} </div> </div> </div> ); }
Result
Loading...
Adaptive Transition Effects
Dynamically adjust transition duration:
Live Editor
function Demo() { const reducedMotion = useReducedMotion(); const [visible, setVisible] = useState(true); const transitionDuration = reducedMotion ? '0s' : '0.3s'; return ( <div> <Button onClick={() => setVisible(!visible)} style={{ marginBottom: '12px' }}> {visible ? 'Hide' : 'Show'} </Button> <div style={{ height: visible ? '200px' : '0', opacity: visible ? 1 : 0, overflow: 'hidden', transition: `all ${transitionDuration}`, backgroundColor: 'var(--ifm-background-surface-color)', border: '1px solid var(--ifm-color-emphasis-300)', borderRadius: '8px', }} > <div style={{ padding: '20px' }}> <h4>Content Area</h4> <p style={{ marginBottom: 0 }}> This area shows or hides based on user animation preferences. {reducedMotion ? ' Currently no transition animation.' : ' Currently has smooth transition animation.'} </p> </div> </div> </div> ); }
Result
Loading...
Loading Animation Adaptation
Provide adapted animation effects for loading states:
Live Editor
function Demo() { const reducedMotion = useReducedMotion(); const [loading, setLoading] = useState(false); const handleLoad = () => { setLoading(true); setTimeout(() => setLoading(false), 2000); }; return ( <div> <Button onClick={handleLoad} disabled={loading} style={{ marginBottom: '16px' }}> {loading ? 'Loading...' : 'Start Loading'} </Button> {loading && ( <div style={{ padding: '32px', backgroundColor: 'var(--ifm-color-emphasis-100)', borderRadius: '8px', textAlign: 'center', }} > <div style={{ display: 'inline-block', width: '40px', height: '40px', border: '4px solid var(--ifm-color-primary-lightest)', borderTopColor: 'var(--ifm-color-primary)', borderRadius: '50%', animation: reducedMotion ? 'none' : 'spin 1s linear infinite', }} /> <div style={{ marginTop: '12px', fontSize: '14px' }}> {reducedMotion ? 'Loading (static)' : 'Loading (spinning animation)'} </div> </div> )} <style>{` @keyframes spin { to { transform: rotate(360deg); } } `}</style> </div> ); }
Result
Loading...
API
Parameters
function useReducedMotion(): boolean
No parameters.
Return Value
Returns a boolean value indicating whether the user has enabled "reduce motion" preference.
boolean
true: User has enabled reduced motionfalse: User allows normal animations
How It Works
Detects user system preferences through the prefers-reduced-motion media query:
@media (prefers-reduced-motion: reduce) {
/* User has enabled reduced motion */
}
Features
- Accessibility: Respects user accessibility preferences
- Auto Response: Automatically updates when user changes settings
- Zero Configuration: Ready to use without additional configuration
- Performance Friendly: Can improve performance for users with reduced motion enabled
Setup Methods
macOS
System Preferences → Accessibility → Display → Reduce Motion
Windows
Settings → Ease of Access → Display → Show animations in Windows
iOS
Settings → Accessibility → Motion → Reduce Motion
Android
Settings → Accessibility → Remove animations
Usage Scenarios
- Transition Animations: Disable or reduce transition effects based on preference
- Loading Animations: Provide static alternatives
- Parallax Effects: Disable scroll effects that may cause discomfort
- Auto-play: Stop automatic carousels and video playback
- Hover Effects: Simplify or remove complex hover animations
- Page Transitions: Use instant transitions instead of fade effects
Best Practices
Progressive Enhancement
const reducedMotion = useReducedMotion();
const animationProps = reducedMotion
? {}
: {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.3 },
};
return <motion.div {...animationProps}>Content</motion.div>;
CSS-in-JS Integration
const reducedMotion = useReducedMotion();
const styles = {
transition: reducedMotion ? 'none' : 'all 0.3s ease',
animation: reducedMotion ? 'none' : 'fadeIn 0.5s',
};
Animation Library Configuration
// Framer Motion
const reducedMotion = useReducedMotion();
<MotionConfig reducedMotion={reducedMotion ? 'always' : 'never'}>
{/* Component tree */}
</MotionConfig>
Notes
- Not all animations should be disabled, only those that may cause discomfort
- Preserve necessary state feedback animations
- Consider providing static alternatives
- Test user experience with reduced motion enabled
- Some functional animations (like progress bars) can be retained
Accessibility
Follows WCAG 2.1 guidelines:
- Success Criterion 2.3.3: Animation from Interactions (AAA level)
- Respect user system preferences
- Provide usable experience without animations