🎬 Animation & Motion Principles
Animation and motion can cause significant issues for users with vestibular disorders, migraines, epilepsy, and sensory sensitivities. Respecting user preferences and providing controls is essential for inclusive design.
Design Guidelines
| Principle |
Description |
Recommendation |
| Respect User Preferences |
Honor system settings |
Use prefers-reduced-motion media query |
| Avoid Flashing Content |
Prevent seizures |
Flash rate below 3 times per second |
| Provide Controls |
Allow user control |
Pause/stop buttons for animations, videos, carousels |
| Avoid Unexpected Animation |
Prevent surprise |
Don't trigger animation without user action |
| Minimize Parallax |
Reduce vestibular issues |
Use sparingly or provide alternative |
| Smooth Transitions |
Avoid jarring changes |
Use eased transitions instead of instant changes |
CSS Implementation
/* Respect user's reduced motion preference */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Smooth transitions */
.smooth-transition {
transition: all 0.3s ease-in-out;
}
/* Controllable animation */
.animated-element {
animation: fadeIn 0.5s ease-out;
}
.animated-element.paused {
animation-play-state: paused;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Avoid parallax for sensitive users */
@media (prefers-reduced-motion: reduce) {
.parallax-element {
transform: none !important;
}
}
Tailwind CSS Classes
<div className="transition-all duration-300 ease-in-out
motion-reduce:transition-none motion-reduce:duration-0">
Content with smooth transitions
</div>
<div className="animate-fade-in motion-reduce:animate-none">
Animated content that respects preferences
</div>
React Component
interface AnimatedCardProps {
children: React.ReactNode;
autoPlay?: boolean;
}
export function AnimatedCard({
children,
autoPlay = false,
}: AnimatedCardProps) {
const [isPaused, setIsPaused] = useState(!autoPlay);
const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
setPrefersReducedMotion(mediaQuery.matches);
const handler = (e: MediaQueryListEvent) =>
setPrefersReducedMotion(e.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, []);
const showAnimation = !prefersReducedMotion && !isPaused;
return (
<div
className={`
card bg-base-100 border border-base-300 p-6
${showAnimation ? "animate-fade-in" : ""}
`}
>
<div className="flex justify-between items-start">
<div>{children}</div>
{!prefersReducedMotion && (
<button
onClick={() => setIsPaused(!isPaused)}
className="btn btn-ghost btn-sm btn-circle"
aria-label={isPaused ? "Play animation" : "Pause animation"}
>
{isPaused ? (
<Play className="w-4 h-4" />
) : (
<Pause className="w-4 h-4" />
)}
</button>
)}
</div>
</div>
);
}
Platform Settings for Reduced Motion
| Platform |
Settings Path |
| Windows 10 |
Settings > Ease of Access > Display > Show animations in Windows |
| Windows 11 |
Settings > Accessibility > Visual effects > Animation effects |
| macOS |
System Preferences > Accessibility > Display > Reduce motion |
| iOS |
Settings > General > Accessibility > Reduce Motion |
| Android 9+ |
Settings > Accessibility > Remove animations |
Seizure Prevention Guidelines
- Flash Rate: Keep below 3 flashes per second
- Flash Area: Limit to small portion of screen
- Contrast: Avoid high-contrast flashing
- Red Flashing: Particularly dangerous - avoid entirely
- Duration: Limit flashing sequences to brief periods
Checklist
Related Resources