Motion-Safe Animation
Write animations inside @media (prefers-reduced-motion: no-preference) — motion is opt-in by default, so reduced-motion users never need overrides. Always safe; always correct.
Quick implementation
/* SAFE PATTERN: write motion inside no-preference query
(motion is opt-in — reduced-motion users never need overrides) */
@media (prefers-reduced-motion: no-preference) {
.card-enter {
animation: rise 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
}
@keyframes rise {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
/* Hover: only move elements when motion is permitted */
@media (prefers-reduced-motion: no-preference) {
.btn .arrow {
transition: transform 0.2s ease;
}
.btn:hover .arrow {
transform: translateX(4px);
}
}
/* Opacity transitions are always acceptable — no spatial motion */
.btn {
transition: opacity 0.15s ease;
}
.btn:hover { opacity: 0.88; }
/* AVOID PATTERN (requires per-animation overrides):
.card-enter { animation: rise 0.5s ... }
@media (prefers-reduced-motion: reduce) {
.card-enter { animation: none; } — easy to forget
} */Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are an accessibility-focused CSS developer. Explain and implement the motion-safe animation pattern.
Requirements:
1. Show the "opt-in" approach: all transform/translate/scale animations live INSIDE @media (prefers-reduced-motion: no-preference). Reduced-motion users get no animation without any override needed.
2. Contrast with the "opt-out" approach: writing animations globally and overriding with @media (prefers-reduced-motion: reduce). Explain why opt-in is safer — opt-out requires you to remember every animation when writing overrides.
3. Implement staggered card entrance animations (animation-delay on nth-child) inside the no-preference query.
4. Show which transitions are always safe (never require a reduced-motion guard):
- opacity transitions (no spatial movement)
- color transitions
- box-shadow transitions that don't involve scale
5. Show which transitions always need a guard:
- transform: translate/scale/rotate
- animation with @keyframes involving spatial movement
- scroll-driven animations with significant parallax
Constraints:
- WCAG 2.3.3 (AAA) covers animations triggered by interaction that last more than 5 seconds. Below 5 seconds is technically exempt at AA but should still be guarded for user comfort.
- Do not put @keyframes inside the media query — keyframes themselves are harmless; it's the animation property that applies them. Put the animation property inside the query.
- The Tailwind equivalent: the motion-safe: variant applies a class only when motion is permitted (e.g., motion-safe:animate-bounce).
Output CSS showing both the opt-in and opt-out approaches side by side, clearly labeled, then the full recommended implementation.Why this matters in 2026
The prefers-reduced-motion: reduce approach — writing animations globally and adding overrides — scales poorly. As a codebase grows, every new animation requires a matching reduced-motion override that is easy to forget. The inverse pattern — writing motion only inside prefers-reduced-motion: no-preference — makes motion opt-in by default. A user with reduced-motion enabled gets a clean, motionless UI automatically, and there is nothing to remember or audit when adding new animations.
The logic
Not all transitions are equal from a vestibular perspective. Spatial movement — translate, scale, rotate, parallax — is the primary trigger for motion sickness and vestibular discomfort. Opacity and color transitions involve no spatial movement and are generally considered safe for all users. The practical rule: gate any animation that moves an element across the screen with a no-preference query; leave opacity and color transitions ungated. This distinction also improves performance — opacity and color are composited cheaply, while spatial transforms on large elements can require repaints.
Accessibility & performance
The no-preference pattern has a useful side effect: browsers that do not support prefers-reduced-motion will never see the animation (it is inside an unsupported query). This is the most conservative possible default — no animation until the browser explicitly signals motion is welcome. For scroll-driven animations and View Transitions, the same pattern applies: place view-transition-name and scroll-driven animation declarations inside the no-preference query so they are inert for reduced-motion users without any additional override.