CSSprefers-reduced-motion

prefers-reduced-motion

prefers-reduced-motion is a media feature that reports whether the user has asked their operating system to minimize animation and motion effects. It's set in system accessibility settings (for example macOS's "Reduce motion", Windows's "Show animations in Windows", or the equivalent on mobile OSes), and CSS can read that OS-level preference directly with a media query — no JavaScript, no cookie banner, no separate toggle to build.

Who this is for
  • Vestibular disorders — large-scale motion, parallax, and spinning effects can trigger real physical symptoms (dizziness, nausea) for some users.

  • Motion sensitivity / migraine triggers — flashing or fast-moving content can provoke migraines or seizures in susceptible users.

  • Simple preference — plenty of users who have no medical reason simply find constant motion distracting and turn it off.

The media query

CSS
/* Default: users who haven't opted for reduced motion get the full effect */
.hero-banner {
  animation: slide-and-spin 2s ease-in-out infinite;
}

/* Users who've asked for reduced motion get little to none */
@media (prefers-reduced-motion: reduce) {
  .hero-banner {
    animation: none;
  }
}

/* You can also target the opposite explicitly */
@media (prefers-reduced-motion: no-preference) {
  .hero-banner {
    animation: slide-and-spin 2s ease-in-out infinite;
  }
}
A worked example — a flashy card entrance

Say a card animates in with a dramatic flip-and-bounce by default. Wrap the flashy version so it's skipped for reduced-motion users, and give them a much calmer (or instant) fallback instead of just nothing:

CSS
.card {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: no-preference) {
  .card {
    animation: flip-bounce-in 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
  }

  @keyframes flip-bounce-in {
    0%   { opacity: 0; transform: rotateY(90deg) scale(0.8); }
    60%  { opacity: 1; transform: rotateY(-10deg) scale(1.05); }
    100% { opacity: 1; transform: rotateY(0) scale(1); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .card {
    /* Still communicate the state change, just without motion */
    transition: opacity 0.15s ease;
  }
}

The card still appears for every user. Users without the preference get the full flip-and-bounce; users who've asked for reduced motion get a quick, gentle fade (or nothing at all) instead of a disorienting rotation.

This is an accessibility requirement, not a nicety
Treat prefers-reduced-motion as seriously as color contrast or keyboard navigation. For some users, ignoring it doesn't just look bad — it makes a page physically unusable. Any animation that's large-scale, parallax-based, auto-playing, or involves spinning/zooming should have a reduced-motion fallback. Small, subtle transitions (a button's hover color easing over 0.2s) are generally fine to leave as-is, but use judgment generously in favor of respecting the preference.
Tip
A safe default pattern for a whole project: wrap ambient/decorative animations in `@media (prefers-reduced-motion: no-preference)` rather than sprinkling `reduce` overrides everywhere — that way reduced motion is the fallback baseline, and adding a new animation without thinking about accessibility can't silently ship unguarded.