Home / Articles / Animation & Motion /
prefers-reduced-motion
Not everyone experiences animation the same way. Reduced motion is an accessibility essential — here is how to implement it correctly.
What triggers it
Users enable "reduce motion" in their operating system settings. Browsers expose this through the prefers-reduced-motion media feature, which has two values: no-preference and reduce.
@media (prefers-reduced-motion: reduce) {
/* remove or soften animations here */
}
The nuclear reset
A common starting point is to strip all animations and transitions globally. This works but is blunt — it removes helpful micro-interactions too.
@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;
}
}
Default-on vs. opt-in motion
A better approach: write your base styles without motion, then add animations only when the user has no preference. This is the opt-in pattern.
/* base: no animation */
.hero-text {
opacity: 1;
transform: none;
}
/* add motion only when allowed */
@media (prefers-reduced-motion: no-preference) {
.hero-text {
animation: slide-up 0.6s ease-out both;
}
}
Which animations to keep
Reduced motion does not mean zero motion. Small, non-vestibular animations can remain because they aid usability without causing discomfort.
- Remove: parallax scrolling, large slide-ins, zooming transitions, auto-playing carousels, infinite spinning loaders.
- Keep (or soften): opacity fades under 200 ms, subtle color changes, focus-ring transitions, progress indicators.
- Replace: swap a slide animation for a simple opacity crossfade.
Using a custom property toggle
Set a duration custom property that collapses to near-zero for reduced motion. Components reference the token instead of hard-coding durations.
:root {
--duration-fast: 0.15s;
--duration-normal: 0.3s;
}
@media (prefers-reduced-motion: reduce) {
:root {
--duration-fast: 0.01ms;
--duration-normal: 0.01ms;
}
}
.card {
transition: box-shadow var(--duration-normal) ease;
}
.card:hover {
box-shadow: 0 8px 24px oklch(0% 0 0 / 0.12);
}
Testing reduced motion
You do not need to change your OS settings every time. Browsers offer developer-tools overrides.
- Chrome: DevTools → Rendering tab → "Emulate CSS media feature prefers-reduced-motion".
- Firefox:
about:config→ setui.prefersReducedMotionto1. - Safari: Use the Accessibility settings in System Preferences.
- Automate with Playwright or Puppeteer:
page.emulateMediaFeatures([{ name: 'prefers-reduced-motion', value: 'reduce' }]).