Home / Articles / Animation & Motion /

animation

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 */
}
People who enable this setting may experience vestibular disorders, motion sickness, migraines, or cognitive overload. Respecting this preference is not optional for accessible design.

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;
  }
}
The opt-in approach is safer because users with reduced motion get a stable experience by default, with no flash of animated content.

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 → set ui.prefersReducedMotion to 1.
  • Safari: Use the Accessibility settings in System Preferences.
  • Automate with Playwright or Puppeteer: page.emulateMediaFeatures([{ name: 'prefers-reduced-motion', value: 'reduce' }]).