# Animations that ignore the reduced-motion setting

Source: https://easeweb.dev/learn/reduced-motion
Topics: Time limits and motion > Animation caused by interaction can be turned off (WCAG 2.3.3)
The fix: Turn off the motion that scrolling and other interaction trigger when people ask for less of it. Either honour the operating system's reduced-motion setting with prefers-reduced-motion in CSS and in scripts, or add a motion switch on the page whose default follows that same setting. Either way, the content still appears, just without moving.
Test with: reduced motion, keyboard, mouse
References: https://www.w3.org/WAI/WCAG22/Understanding/animation-from-interactions.html https://www.w3.org/WAI/WCAG22/Techniques/css/C39 https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia

This is a recreated teaching example, not a finding about a named client. The code is a starting pattern: test it in your own product. No completed assistive-technology test is claimed here.

## What happens

A shop's "About us" page tells its story in five sections. As the visitor scrolls, each section starts lower down, invisible, then fades in while sliding up into place. Many marketing pages and site templates work this way, often through an "animate on scroll" library, and the content is always moving while the visitor scrolls. The same failure turns up in drawers that slide in, modals that zoom open, accordions that glide open and carousels that sweep sideways; this guide uses the scroll reveal because it is the most common. A visitor has turned on Reduce motion in their operating system because movement like this makes them dizzy. Every section still rises in.

## Why it fails

Success Criterion 2.3.3 Animation from Interactions (Level AAA) asks that motion animation triggered by interaction can be disabled, unless the animation is essential to the function or the information. Scrolling is an interaction, and the Understanding document names scroll-triggered movement and parallax as examples. The slide-in adds nothing the content needs: the text says the same thing whether it arrives or is simply there. The stylesheet and the script never check `prefers-reduced-motion`, and the page offers no switch of its own, so there is no way to turn the movement off.

## Who is affected

People with vestibular disorders, migraine or concussion can feel dizziness, nausea or headache from large movement, parallax and zooming, sometimes for hours afterwards, and scroll-linked movement makes it worse because it never stops while they read. Many of them have already said what they need, once, in their system settings.

## What automation and AI miss

A static scan sees a CSS class or a library include, not the movement it produces, and it can't judge whether movement is essential. Many scanners never scroll, so scroll-triggered animation never runs. AI-written fixes often add a reduced-motion block for CSS transitions and miss the library that drives the effect from JavaScript. Others "fix" it by removing the class that shows the section, so with motion reduced the content stays invisible at `opacity: 0`.

## Before: sections that slide in, with no way to turn it off

```css
.reveal {
  opacity: 0;
  transform: translateY(80px);
  transition: opacity 800ms ease, transform 800ms ease;
}
.reveal.in-view {
  opacity: 1;
  transform: none;
}
```

```js
const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) entry.target.classList.add('in-view');
  }
});
document.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
```

Every section moves for every visitor. Nothing asks whether the visitor wants less motion, and the page has no setting of its own.

## After: honour the reduced-motion setting

```css
/* Visible and still by default. */
.reveal {
  opacity: 1;
}
/* Movement only for people who haven't asked for less. */
@media (prefers-reduced-motion: no-preference) {
  .reveal:not(.in-view) {
    opacity: 0;
    transform: translateY(80px);
  }
  .reveal {
    transition: opacity 800ms ease, transform 800ms ease;
  }
}
```

The hidden, lowered starting state and the slide now exist only inside `no-preference`. When the system asks for reduced motion, or a browser can't report the setting, the sections are simply there. Nothing has to be undone, so an animation added later can't be forgotten. The observer script can stay as it is. If the effect comes from a library, turn on its own reduced-motion option as well, because many libraries set transforms from JavaScript, where this CSS can't reach them.

## After: a motion switch on the page that starts from the system setting

```html
<button type="button" id="motion-toggle" aria-pressed="false">Reduce motion</button>
```

```js
const toggle = document.querySelector('#motion-toggle');
const saved = localStorage.getItem('reduce-motion');
let reduce = saved === null
  ? matchMedia('(prefers-reduced-motion: reduce)').matches
  : saved === 'true';

function apply() {
  toggle.setAttribute('aria-pressed', String(reduce));
  document.documentElement.classList.toggle('reduce-motion', reduce);
}
toggle.addEventListener('click', () => {
  reduce = !reduce;
  localStorage.setItem('reduce-motion', String(reduce));
  apply();
});
apply();
```

```css
.reduce-motion .reveal {
  transform: none;
  transition: opacity 200ms linear;
}
```

The page has its own "Reduce motion" switch, a native toggle button with its state in `aria-pressed`. Until someone presses it, it follows the system setting, so people who set it there are covered without doing anything. People who never found the system setting, or who use a shared or work computer they can't change, can turn motion off here. The choice is saved, so it holds on the next page. This only works if the switch is easy to find, for example in the header, and if the script runs before the first section animates.

## Which option to use

Both pass. The media query is a few lines, needs no interface, and respects a choice the visitor has already made for every site, but it only helps people who know about the system setting and are allowed to change it. The switch on the page reaches those people too, at the cost of a control to design, place and label, and a stored preference to read before anything moves. Choose the media query alone when the motion is light; add the switch when motion is a large part of the design, the site is used on shared machines, or visitors ask for it.

## Implementation decisions

Reduce motion means removing movement, not removing content. Short fades, colour changes and instant changes are generally fine; slides, zooms, spins, parallax and long smooth scrolls are what to remove. Keep animation that is essential, such as a drag that follows the pointer.

The same approach covers every other component that moves. Each still opens, closes or changes; only the travel goes:

- Drawer: appears in place or fades, instead of sliding in.
- Modal: fades, instead of zooming or dropping open.
- Accordion: opens at once, instead of animating its height.
- Carousel: changes slides without sweeping sideways.
- Page transition: swaps views without sliding.

Content that starts at `opacity: 0` and waits for a script is fragile in any mode: if the script fails or the observer never fires, the section never appears. Keep the hidden starting state inside `@media (prefers-reduced-motion: no-preference)`, as the first fix does, or add it from the script.

Animation in JavaScript is where most misses are. Check scroll-reveal and parallax libraries, carousels, `scrollIntoView({ behavior: 'smooth' })`, `scroll-behavior: smooth` and route transitions in single-page apps, and turn on each library's own reduced-motion option. A blanket rule that sets every `animation-duration` and `transition-duration` close to zero is a useful safety net, but it does nothing about a script that moves things itself, and it can break code that waits for `transitionend`.

Motion that starts without interaction, such as autoplaying carousels and background video, is covered by 2.2.2 Pause, Stop, Hide rather than this criterion. It is worth turning off under reduced motion as well.

## Verify the fix

1. Turn on Reduce motion in the operating system, or emulate `prefers-reduced-motion: reduce` in the browser's developer tools; if the page has its own switch, also test it on, starting from a system setting of no preference.
2. Scroll every page with the mouse, the keyboard and in-page links, and use every interactive feature, and confirm nothing slides, zooms, spins or parallaxes.
3. Search the scripts and libraries for animation and smooth scrolling, and confirm each one checks the same setting before it moves anything.
4. Confirm every section still appears without the movement, and still appears with JavaScript turned off or failing.

Record the OS, browser and assistive-technology versions, the build, the date, and the actual result of each step. Repeat after adding a new animation or updating an animation library.

## Limits

This is a starting pattern, not a guarantee for every application. 2.3.3 is a Level AAA criterion, so many conformance targets do not require it, but the reduced-motion setting is a direct request from the visitor and is cheap to honour. Which movements make someone unwell varies from person to person; testing with people who use the setting tells you more than any rule.
