Chapter 11 of 11Part 4 · Finish
Prefers reduced motion: animation for every reader
Respect readers who ask for less motion: what WCAG asks, how to read the setting in CSS and GSAP, and what to keep or remove.
9 minBy Zuzo StudioUpdated
In this chapter you will
- explain who the reduced motion setting is for and what WCAG 2.2.2 and 2.3.3 require
- read prefers-reduced-motion in CSS, in plain JavaScript and with gsap.matchMedia()
- decide which effects to keep, replace with a fade or remove for reduced motion
Before you startThis builds on SVG line animation: make a path draw itself.
Every chapter so far has added movement. This one decides what happens when a reader asks for less. Phones and computers have a setting that says "reduce motion", and browsers expose it to your CSS and JavaScript. When a reader prefers reduced motion, the respectful answer is not a blank, frozen page. It is the same page with the movement taken out and the meaning kept. This chapter covers who the setting is for, what WCAG asks of you, how to read the preference in CSS and in GSAP, and a clear list of what to keep and what to remove. Then you wrap the whole hero project in it.
01Who reduced motion is for
The W3C's explanation of Animation from Interactions puts it plainly: people can get sick from motion effects. For people with vestibular (inner ear) disorders, motion on screen can trigger dizziness, nausea and headaches. The W3C names two common culprits: elements that move as you scroll, beyond the normal movement of scrolling itself, and parallax, where backgrounds move at a different speed from the foreground.
That is exactly the kind of motion this course has taught you to build. A reveal that slides cards up as you scroll, a scrubbed shape that rotates with the scrollbar, a hero that zooms in: each is fine for most readers and a real problem for some.
Vestibular disorders are not the only reason. Some people turn the setting on because animation distracts them, because it triggers migraines, or because their phone feels faster without it. You do not need to know why. The setting is the reader telling you what works for them, so treat it as an instruction, not a hint.
02What WCAG asks for
Two success criteria in WCAG 2.2 cover motion directly.
| Criterion | Level | What it requires |
|---|---|---|
| 2.2.2 Pause, Stop, Hide | A | Anything that moves, blinks or scrolls automatically, lasts more than five seconds and sits alongside other content needs a way to pause, stop or hide it |
| 2.3.3 Animation from Interactions | AAA | Motion triggered by an interaction, such as scrolling or clicking, can be turned off unless it is essential |
Pause, Stop, Hide is level A, the baseline every site should meet, and it applies to every reader, whatever their settings. A looping animation, an autoplaying video or a rotating carousel that runs for more than five seconds needs a visible control. The simplest fix for decorative loops is to make them stop on their own: the scroll cue from chapter 5 can run three times and then rest, well inside five seconds.
Animation from Interactions is level AAA, which many sites do not formally target. Meet it anyway. Honouring prefers-reduced-motion is the standard way to do so, it costs a few lines of code, and it makes the difference between a page someone can read and a page that makes them feel ill. Our website accessibility checklist lists the rest of what an accessible launch needs.
03Prefers reduced motion in CSS
The prefers-reduced-motion media query has two values. MDN's prefers-reduced-motion reference defines reduce as the reader having turned the setting on and no-preference as them not having said anything. It also lists where the setting lives on each system: on iOS under Settings, Accessibility, Motion; on Android as Remove animations under Accessibility; on Windows 11 as Animation effects under Accessibility, Visual effects; and on recent macOS as Reduce motion under Accessibility.
There are two ways to use it.
Swap movement for a fade
Keep the animation, but change what it does. A card that rose and faded in now only fades:
@keyframes rise { from { opacity: 0; transform: translateY(2rem); } }
@keyframes fade { from { opacity: 0; } }
.card { animation: rise 0.6s ease-out both; }
@media (prefers-reduced-motion: reduce) {
.card { animation-name: fade; animation-duration: 0.3s; }
}
This is the approach to aim for. The reader still gets the signal that something has arrived, without anything moving across the screen.
A safety net for everything else
Many stylesheets also include a blanket rule that cuts every CSS animation and transition to almost nothing:
@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;
}
}
It is a useful net for third party widgets you cannot edit, and the scroll-behavior line stops smooth scrolling on anchor links. But it is blunt. It removes harmless colour transitions and the fades you designed on purpose, and it does nothing at all to animation run by JavaScript, which is where GSAP lives. Use it as a backstop, not as your reduced motion design.
04Prefers reduced motion in JavaScript and GSAP
In plain JavaScript, window.matchMedia() reads the same query, and a change listener tells you if the reader switches the setting while the page is open:
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
if (!reduce.matches) startParallax();
reduce.addEventListener('change', () => location.reload());
GSAP has a tidier tool. gsap.matchMedia() runs a setup function when a media query matches, and its docs explain that when the query stops matching, every animation and ScrollTrigger created inside that function is reverted automatically. Pass it a conditions object and you get a boolean for each:
const mm = gsap.matchMedia();
mm.add({ full: '(prefers-reduced-motion: no-preference)', reduce: '(prefers-reduced-motion: reduce)' }, (ctx) => {
const { reduce } = ctx.conditions;
gsap.from('.feature', {
opacity: 0,
y: reduce ? 0 : 40,
duration: reduce ? 0.3 : 0.6,
stagger: reduce ? 0 : 0.1,
scrollTrigger: { trigger: '.features', start: 'top 80%', once: true },
});
});
One function, two behaviours: full readers get the rise and stagger from scroll animation, reduced readers get a short fade. If someone changes their setting, GSAP tears down the old version and builds the new one.
05What to keep and what to remove
Reduced motion means less movement, not less design. A useful test: does the effect move something across the screen, change its size, or tie it to the scroll? If yes, remove it or turn it into a fade. If it changes colour, opacity or nothing spatial, it can usually stay.
| Effect | With reduced motion |
|---|---|
| Fades (opacity) | Keep, and keep them short |
| Colour and background changes on hover and focus | Keep |
| Focus outlines | Always keep |
| Slides and rises (translate) | Replace with a fade |
| Zooms, scale and rotation | Remove, or replace with a fade |
| Parallax and scrubbed scroll effects | Remove; show the finished state |
| Pinned scroll sections | Unpin and let the page scroll normally |
| Looping accents, autoplay video, carousels | Stop them; offer a pause control for everyone |
| Smooth scrolling | Use the browser's normal jump |
| SVG line draws | Show the line already drawn, or fade it in |
The decision to cut motion here should be easy, because the course has asked every animation to justify itself since why motion. If an effect only exists for movement, the reduced version can drop it without losing anything the reader needs. Our animation performance checklist also treats reduced motion as part of performance: less animation is less work for a slow phone.
06Finishing the course project
Your hero now has a motion brief, timing tokens, staggered words, considered hovers, a scroll cue, a GSAP timeline, a scroll reveal and an underline that draws itself. The last step is making all of it behave for the reader who asked for less.
mm.add({ full: '(prefers-reduced-motion: no-preference)', reduce: '(prefers-reduced-motion: reduce)' }, (ctx) => {
if (ctx.conditions.reduce) {
gsap.set('.underline path', { strokeDashoffset: 0 });
gsap.from('.button', { opacity: 0, duration: 0.3 });
return;
}
buildHeroTimeline();
buildScrollReveal();
});
Check your understanding
3 questions
Key takeaways
- For people with vestibular disorders, scroll linked movement and parallax can cause dizziness, nausea and headaches.
- WCAG 2.2.2 (level A) needs a way to pause anything that moves automatically for more than five seconds, for every reader.
- Under prefers-reduced-motion: reduce, replace movement with short fades rather than removing content or leaving it hidden.
- A blanket CSS reset is a useful backstop, but it does not touch JavaScript animation, so GSAP needs gsap.matchMedia() or a matchMedia check.
- Keep colour, opacity and focus states; remove or fade translation, scale, rotation, parallax, pinning and smooth scrolling.
Sources
- Understanding SC 2.3.3: Animation from InteractionsW3C Web Accessibility Initiative, checked
- Understanding SC 2.2.2: Pause, Stop, HideW3C Web Accessibility Initiative, checked
- prefers-reduced-motion CSS media featureMDN Web Docs, checked
- gsap.matchMedia()GSAP, checked