Accessible animations respect user preferences and ensure your content works for everyone, including users with vestibular disorders, seizure disorders, or those using assistive technologies.
Respecting motion preferences
Many users enable “Reduce Motion” in their operating system settings due to vestibular disorders or motion sensitivity.
CSS approach
@media (prefers-reduced-motion: reduce) {
* {
animation-duration : 0.01 ms !important ;
animation-iteration-count : 1 !important ;
transition-duration : 0.01 ms !important ;
}
}
JavaScript detection
const prefersReducedMotion = window . matchMedia (
'(prefers-reduced-motion: reduce)'
). matches
if ( prefersReducedMotion ) {
// Use instant transitions or simpler animations
}
React implementation
The Animation Playground includes an accessibility control:
function AccessibilityControls () {
const [ prefersReducedMotion , setPrefersReducedMotion ] = useState ( false )
return (
< div className = "flex items-center gap-4 p-4 bg-yellow-50 rounded-lg" >
< label className = "flex items-center gap-2" >
< input
type = "checkbox"
checked = { prefersReducedMotion }
onChange = { ( e ) => setPrefersReducedMotion ( e . target . checked ) }
/>
< span className = "text-sm" > Simulate prefers-reduced-motion </ span >
</ label >
< InfoTooltip
title = "Accessibility"
content = "Users can enable reduced motion in their OS settings. Your animations should respect this preference."
/>
</ div >
)
}
Framer Motion support
import { motion , useReducedMotion } from 'framer-motion'
function Component () {
const shouldReduceMotion = useReducedMotion ()
return (
< motion.div
animate = { { x: shouldReduceMotion ? 0 : 100 } }
transition = { { duration: shouldReduceMotion ? 0 : 0.5 } }
/>
)
}
Safe animation practices
Avoid flashing Never flash more than 3 times per second - can trigger seizures
Limit parallax Excessive parallax scrolling can cause nausea
Provide pause controls For auto-playing animations, include pause/play controls
Keep animations subtle Large, fast movements are more disorienting
ARIA considerations
Animated content announcements
< div role = "status" aria-live = "polite" aria-atomic = "true" >
{ isAnimating ? 'Loading...' : 'Content loaded' }
</ div >
Focus management
< motion.div
initial = { { opacity: 0 } }
animate = { { opacity: 1 } }
onAnimationComplete = { () => {
// Focus the first interactive element when animation completes
firstInputRef . current ?. focus ()
} }
>
< input ref = { firstInputRef } />
</ motion.div >
Testing checklist
Enable reduced motion
Test with OS-level reduced motion enabled:
macOS: System Preferences → Accessibility → Display → Reduce motion
Windows: Settings → Ease of Access → Display → Show animations
iOS/Android: Settings → Accessibility → Reduce motion
Use keyboard navigation
Ensure all interactive elements are reachable and usable with keyboard only. Animations shouldn’t interfere with Tab order.
Test with screen readers
Verify that screen readers correctly announce state changes triggered by animations.
Check color contrast
Animated elements should maintain WCAG AA contrast ratios (4.5:1 for text).
Implementation patterns
Conditional animations
const variants = {
initial: { opacity: 0 , y: 20 },
animate: {
opacity: 1 ,
y: 0 ,
transition: {
duration: prefersReducedMotion ? 0 : 0.5 ,
ease: 'easeOut'
}
}
}
Fallback to crossfade
const transition = prefersReducedMotion
? { duration: 0.2 , ease: 'linear' } // Quick crossfade
: { duration: 0.5 , ease: 'easeInOut' , type: 'spring' } // Full animation
Don’t completely remove animations for users with motion preferences. Use simpler, quicker transitions instead of instant changes.
Resources