Motion and `prefers-reduced-motion`

Applies to a11y.reduced-motion.

Covers a11y.reduced-motion. Related: perf.animation-cost.

Why this one is a blocker

Vestibular disorders are common, and large-scale motion is not a matter of taste for the people who have one. Parallax, scroll-jacking, zooming transitions and full-screen wipes can cause nausea and dizziness that lasts hours. The operating system already carries the answer - the visitor has told their machine they want less motion - and all the site has to do is listen.

These are animation-heavy sites, which makes this section load-bearing rather than a formality.

What reduced motion means

Not "the same animation, faster." It means the content arrives in its final state. Fades and colour changes are generally fine. Movement across the screen, scale, rotation and parallax are not.

const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

if (reduceMotion) {
  gsap.set(elements, { clearProps: "all" });  // final state, no timeline
  return;
}
@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;
  }
}

The CSS blanket rule is a safety net, not the implementation. It cannot reach anything a JavaScript timeline drives, which on these sites is most of it.

Where it gets missed

Every module has to check. A project with thirty-five animated modules and thirty-two checks has three that will move, and those three are usually the biggest ones, because the biggest effects get written first and refactored last.

Check the list explicitly:

The loader and the page transition are the two most commonly forgotten, because they run before the module system has finished starting.

Beyond the media query

prefers-reduced-motion is one requirement. Two more sit alongside it in WCAG 2.1 AA:

And one that is not a rule but should be: if a scroll-driven reveal controls the largest element above the fold, it is also destroying the LCP measurement. See docs/perf/measuring.md.

Testing it

macOS: System Settings, Accessibility, Display, Reduce motion. Windows: Settings, Accessibility, Visual effects, Animation effects. Chrome DevTools: Rendering panel, "Emulate CSS prefers-reduced-motion".

Turn it on, reload with a cleared cache, and scroll the whole site. Nothing should move.