Accessibility · Perception

How prefers-reduced-motion Reaches an Animation and What It Cannot Stop

The media query reports a user setting, so authors must honour it in CSS and in script; which motion triggers vestibular symptoms, and why duration is the wrong knob to turn.

Motion on screen is not only a distraction. Large-area movement, parallax, and zooming transitions can trigger dizziness, nausea, and headache in people with vestibular disorders, and the effect persists after the motion stops. The mechanism is a conflict between what the visual system reports and what the inner ear reports: the eyes see the viewer accelerating through space, the vestibular system reports no acceleration, and the mismatch produces the symptoms. The size of the moving region and its speed matter more than the total duration.

The platform exposes one user preference for this, and it is a preference rather than a guarantee. prefers-reduced-motion reports that the user has asked the operating system to minimise animation. It does not report which symptoms they have, it does not distinguish a mild dislike of fades from a vestibular condition, and it does not enforce anything. Authors who treat it as a signal to honour get the behaviour right; authors who treat it as a hint that may be ignored produce a page that animates anyway.

Two values, one of which is a default

The query takes no-preference and reduce. The default in the absence of any user setting is no-preference, so a rule written as @media (prefers-reduced-motion: reduce) is inert for most users and active for those who changed the setting. This asymmetry matters for how the rule is written: the common pattern is to author the animated version by default and then override it inside the query, which means the reduced state is a set of subtractions from the animated state. Subtractions are easy to get wrong, because every new animation added later has to be subtracted again.

The inverted pattern — author the static version by default, and add motion inside @media (prefers-reduced-motion: no-preference) — fails differently. Browsers that do not support the query, and there are still some in embedded contexts, treat an unknown feature as false, so those users get the static version. That is the safe direction for a failure, and it is why the no-preference framing is often recommended even though it inverts the usual reading of a media query.

The query is also not the only relevant one. prefers-contrast and prefers-reduced-transparency sit alongside it, and a design that respects one but not the others is only partially adapting. They are independent settings; a user may reduce motion and leave contrast alone.

What CSS can and cannot switch off

Inside the query, the blunt instrument is to neutralise durations:

@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;
  }
}

Setting the duration to a very small nonzero value rather than to zero is a convention with two effects. It makes the animation or transition complete on the next frame, so the final keyframe is applied and the element ends in the state the animated version would have reached; and it keeps the animation running long enough to be observable, so callbacks that depend on the animation having started rather than only on its result still fire. Disabling the animation outright with animation: none discards the keyframes, and any element whose resting position came from the final keyframe jumps back to its base style. The animation-iteration-count: 1 is required separately because an infinite animation with a 0.01 ms duration still repeats thousands of times per second, which is worse than the original.

scroll-behavior: auto belongs in the list because smooth scrolling is motion, and a long programmatic scroll across a page is exactly the kind of large-area movement that causes trouble. The property is often set globally on html for polish and then forgotten.

What CSS cannot do is remove motion that is not expressed as a CSS animation or transition. A requestAnimationFrame loop that moves a canvas, a video that autoplays, a <marquee>-equivalent built in script, a smooth-scroll implementation that animates scrollTop frame by frame — none of these are affected by the stylesheet. The duration override also cannot remove motion that is a consequence of layout, such as an element that shifts because content loaded above it; that is not an animation, it is a reflow.

The script side

Script-driven motion has to read the same query and branch. The matchMedia interface returns an object with a matches boolean and fires a change event when the setting changes while the page is open, which happens when a user toggles the system setting or when a laptop switches to a battery-saver profile that alters it.

const query = window.matchMedia('(prefers-reduced-motion: reduce)');
let reduced = query.matches;

query.addEventListener('change', (e) => {
  reduced = e.matches;
  // stop or restart the loop here rather than mid-frame
});

function tick(t) {
  if (!reduced) { /* advance the animated state */ }
  requestAnimationFrame(tick);
}

Reading matches once at startup is the common shortcut and it is wrong for long-lived pages. A setting changed mid-session either goes unnoticed, so the animation continues, or the reverse: a user turns motion back on and the page stays frozen. The change event is what makes the branch live.

The other script-side trap is the pattern of feature detection by exception. Code that assumes the query is supported and calls addEventListener on the result of matchMedia works in current browsers, but older implementations only had addListener, and code paths written for those can leave two listeners registered. Testing matches directly is portable; the listener registration is the part that needs care.

Which motion actually causes harm

Not all animation is equivalent, and a design that reduces everything loses affordances that help. The distinction that matters is between motion that simulates the viewer moving through space and motion that does not.

Parallax scrolling, where background layers move at a different rate from the foreground, simulates depth and camera movement and is among the strongest triggers. Zoom transitions that scale a whole page, especially those that animate from a small element to a full-screen view, simulate forward travel. Large-area slides where a full viewport translates horizontally are similar. Auto-playing background video and looping animated backgrounds fall in the same category because the motion covers most of the field of view.

Small, contained motion is different. A spinner that rotates within a 32 px box, a progress bar that fills, a subtle color transition on a button, a tooltip that fades in over 150 ms — these occupy a small visual area and do not imply self-motion. Removing them is unnecessary, and a blanket * rule that removes all of them makes the interface feel broken without protecting anyone.

That argues for a two-tier approach: the global duration override as a floor, plus targeted removal of the specific effects that simulate movement. The targeted removal is the part that has to be written deliberately, because it is the part a global rule cannot infer.

Where the preference is not available at all

Assistive technology and platform settings do not always reach the page. A user who needs reduced motion on a device whose browser does not expose the query gets the animated version, and there is no author-side workaround. The corresponding mitigation is to keep the most provocative motion behind an explicit user action — click to play, a control that starts the animation — rather than starting it on load. WCAG’s requirement that motion triggered by interaction be disableable reflects the same reasoning: if the motion only begins after a deliberate act, the user has a chance to avoid it, and an explicit stop control covers the rest.

Autoplaying media is a separate case with its own rules, and one that overlaps with the timing work that captions require, since a paused video is also a video whose captions do not run away from the reader. A background video that loops behind text raises both problems at once: it is motion, and it is an uncontrolled backdrop, which is the same conflict as text over a photograph.

For motion that carries meaning — a transition that shows where a panel came from, an animation that demonstrates a physical process — removal needs a replacement rather than a deletion. A static before-and-after, a step sequence, or a diagram with labelled states conveys the same relationship without the movement. The test is whether the information survives when the motion is gone; if it does not, the motion was the content and the alternative has to be built.

References

  1. Media Queries Level 5 — W3C
  2. Web Content Accessibility Guidelines (WCAG) 2.2 — Animation from Interactions — W3C WAI
  3. CSS Animations Level 1 — W3C
  4. CSS Transitions — W3C