Why Motion Preferences Matter

Motion on the web isn't experienced uniformly. Smooth animations for some users can be distracting, nausea-inducing, or even seizure-triggering for others. Motion-heavy sites can also drain mobile batteries faster and consume more data, particularly with autoplaying video. These concerns are why modern operating systems expose accessibility settings that let users declare their motion preferences at the system level.

The prefers-reduced-motion media query, part of the Media Queries Level 5 specification, lets CSS detect those system-level preferences and respond accordingly. It accepts two values: reduce and no-preference. Support is solid across all modern browsers.

One approach is to disable animation when the user requests reduced motion:

.some-element {
  animation: bounce 1200ms;
}

@media (prefers-reduced-motion: reduce) {
  .some-element {
    animation: none;
  }
}

Alternatively, you can apply animation only when no preference is stated. This inverted pattern reduces the amount of CSS you write and makes it harder to accidentally omit reduced-motion handling:

@media (prefers-reduced-motion: no-preference) {
  .some-element {
    animation: bounce 1200ms;
  }
}

An additional benefit: browsers that don't support the query will ignore the rule entirely, leaving the motion-free version visible.

Choosing the Right Pattern

Unlike min-width versus max-width conventions in responsive design, there's no established single approach for structuring reduced-motion styles. The "apply only under no-preference" pattern tends to be the better choice for the reasons above — less code and easier coverage. Whatever pattern your team selects, consistent communication is essential for maintaining accessibility across a codebase.

Beyond CSS Animations

The query applies to more than keyframes and transitions. Smooth scrolling is a good example. Setting scroll-behavior: smooth on the html element makes in-page anchor navigation glide rather than jump, though Safari doesn't currently support it:

html {
  scroll-behavior: smooth;
}

Long pages can scroll extremely fast with this setting, a jarring experience for users with motion sensitivity. Wrapping the rule in a media query prevents that behavior for those users:

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

JavaScript Detection

Motion sometimes needs to be handled in JavaScript. The matchMedia API provides equivalent detection:

/* Set the media query */
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')

button.addEventListener('click', () => {
  /* If the media query matches, set scroll behavior variable to 'auto', 
  otherwise set it to 'smooth' */
  const behavior = prefersReducedMotion.matches ? 'auto' : 'smooth'

  /* When the button is clicked, the user will be scrolled to the top */
  window.scrollTo({
    x: 0,
    y: 0,
    behavior
  })
})

The same principle extends to libraries. Checking the user's preference before loading animation-heavy dependencies can yield a performance win by avoiding unnecessary downloads entirely. In the snippet below, the function returns early for reduced-motion users, skipping a dynamic import of the Greensock library:

const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')

const loadGSAPAndInitAnimations = () => {
  /* If user prefers reduced motion, do nothing */
  if (prefersReducedMotion.matches) return
  
  /* Otherwise, import the GSAP module and initialize animations */
  import('gsap').then((object) => {
    const gsap = object.default
    /* Initialize animations with GSAP here */
  })
}

loadGSAPAndInitAnimations()

Reduced Motion Isn't No Motion

Respecting reduced-motion preferences doesn't mean eliminating all visual feedback. Users still need clear, accessible indicators that an action occurred. If you strip away an elaborate hover transition, provide a subtler alternative that still signals the state change — for example, a gallery item that swaps a dramatic transition for a simple fade on hover or focus:

See the Pen [Gallery with prefers-reduced-motion](https://codepen.io/smashingmag/pen/KKvMqaL) by Michelle Barker.

See the Pen Gallery with prefers-reduced-motion by Michelle Barker.

Small transformations are also generally acceptable. A button arrow shifting a few pixels on hover is unlikely to trouble motion-sensitive users, and it conveys state more effectively than a color change alone.

It's tempting to reset everything at once with a catch-all rule that kills all transitions and animations:

@media screen and (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

That's better than ignoring user preferences, but it also prevents you from tailoring subtle transitions where they help. Consider a button that scales up on hover — if you transition colors and scale together, reduced-motion users get no transition whatsoever:

button {
  background-color: hotpink;
  transition: color 300ms, background-color 300ms, transform 500ms cubic-bezier(.44, .23, .47, 1.27);
}

button:hover,
button:focus {
  background-color: darkviolet;
  color: white;
  transform: scale(1.2);
}

@media screen and (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }

  button {
    /* Even though we would still like to transition the colors of our button, the following rule will have no effect */
    transition: color 200ms, background-color 200ms;
  }
        
  button:hover,
  button:focus {
    /* Preventing the button scaling on hover */
    transform: scale(1);
  }
}

An abrupt color snap can feel more jarring than a brief transition. Styling for reduced motion on a case-by-case basis generally produces better results. One way to do this is with a custom property controlling transition duration, letting you toggle the scale transition without rewriting the whole declaration.

When Removal Is Correct

Eric Bailey notes that not all devices can render animation smoothly. On hardware with low refresh rates, animations can appear janky, and removing them entirely is sometimes the right call. The update media feature can help identify these devices:

@media screen and
  (prefers-reduced-motion: reduce), 
  (update: slow) {
  * {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

Thinking at Page Level

Component-level decisions can mislead. An innocuous animation at the component level becomes significant when repeated across a page among other moving parts. Organizing all animations behind a single media query — for example, in one CSS file loaded only when no-preference applies — helps visualize the full-page experience and tailor reduced-motion styles accordingly.

Explicit Motion Controls

prefers-reduced-motion only serves users aware of their system settings. Many people don't know the feature exists, some use borrowed machines, and others might enjoy motion on most sites but find heavily animated pages overwhelming. Adjusting system preferences just for one site is friction.

In those cases, an on-page toggle for motion is a worthwhile addition. Implemented with JavaScript, such a toggle can start from the user's system preference (read via prefers-reduced-motion) and let them switch by clicking a button. Adding a class to body applies the chosen styles. Storing the choice in local storage preserves it across visits.

See the Pen [Reduced-motion toggle](https://codepen.io/smashingmag/pen/porEQLB) by Michelle Barker.

See the Pen Reduced-motion toggle by Michelle Barker.

Custom Properties for Pausing

A useful technique in those demos is controlling animation state via a custom property. Set --playState to paused initially:

.circle {
  animation-play-state: var(--playState, paused);
}

Then override it to running when the user's system preferences allow motion:

@media (prefers-reduced-motion: no-preference) {
  body {
    --playState: running;
  }
}

When the button is clicked, the custom property is updated on the body, toggling every animation that references it:

// This will pause all animations that use the `--playState` custom property
document.body.style.setProperty('--playState', 'paused')

Because the property is set on body, it inherits to descendant elements. One benefit of this approach is animations pause in place when toggled, rather than jumping back to their initial states, which can be jarring. Careful attention to the toggle's accessibility is needed — screen readers may not announce changed button text, so using role="switch" and toggling aria-checked between on and off ensures the state is communicated.

Component-Level Video Toggle

Sometimes the toggle belongs at the component level. An autoplaying background video, for instance, shouldn't autoplay for users who prefer reduced motion, but they should still have a way to play it if they choose — just as users without a stated preference need a way to pause it. You can conditionally set the autoplay attribute based on motion preference and provide a custom play/pause button for both cases:

See the Pen [Video with motion preference](https://codepen.io/smashingmag/pen/qBXNjqR) by Michelle Barker.

See the Pen Video with motion preference by Michelle Barker.

The <picture> Element Technique

Chris Coyier describes combining the <picture> element with media queries to serve static images to reduced-motion users instead of animated GIFs. The significant advantage is bandwidth: users who prefer reduced motion never download the much larger GIF file. The limitation is that there's no way to switch back once downloaded. A modified version adds a toggle between the static and animated versions, though Chrome appears to re-download the GIF on each switch while other browsers cache it:

See the Pen [Prefers Reduction Motion Technique PLUS! [forked]](https://codepen.io/smashingmag/pen/porbPXG) by Michelle Barker.

See the Pen Prefers Reduction Motion Technique PLUS! [forked] by Michelle Barker.

Regardless of that quirk, serving GIFs according to motion preference is a more respectful pattern for a format users can find frustrating.

A Missing Layer In Motion Accessibility

prefers-reduced-motion is supported across all modern browsers, and the reduced-motion-first approach means that older or non-supporting environments simply receive a reduced-motion fallback. There is no practical reason to hold off on implementing it for accessibility.

Custom motion toggles, where developers build their own UI control, can significantly help users who are not aware of the operating system or browser setting. However, they create a consistency problem — users must hunt for a different toggle location on every site they visit.

The missing piece is the browser itself. A built-in, standard reduced-motion control in browser settings — placed somewhere users can consistently find across all websites — would improve adoption for end users and nudge developers to pay more attention to motion accessibility in their work.

Smashing Editorial

Resources On Motion Preferences