Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 7.2Intermediate ~19 min · shot 2 of 7

prefers-reduced-motion in CSS

In one line

The prefers-reduced-motion media query tells your CSS that the visitor asked their device for less movement. The rule is to reduce, not remove: swap travel for a fade and keep every state change.

0/7 · 0%

Think of it like this

A friend asks you to turn the music down. You do not unplug the speaker, because then there is no music and no way to tell the party is on. You lower the volume. Reduced motion works the same way: the page still shows what changed, only with less movement.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

media query
A condition around CSS rules, such as @media (prefers-reduced-motion: reduce). The rules apply only while it is true.
no-preference
The value meaning the visitor has not asked for less motion.
reduce
The value meaning the visitor switched on reduced motion in their device settings.
opt-out / opt-in
Opt-out: movement is the default and reduce removes it. Opt-in: movement exists only inside no-preference.

Step by step

01Find what moves

List every animation that moves or scales something: slides, zooms, spins, bounces, parallax. Fades and colour changes can stay.

02Give each a calm twin

For each one decide the replacement: usually a fade of the same length. Keep the state change itself (the item appears, the panel opens).

03Write the rule

Swap the animation name inside @media (prefers-reduced-motion: reduce), or write the movement only inside no-preference. Put the shared keyframes outside any query.

04Test the code that waits

Search your JavaScript for transitionend and animationend. Run the page with the Reduced motion switch on and check that panels still open, close and get removed.

Try it yourself

  1. 1

    Switch to reduced and read the tracks

    In "Reduce, don't remove", press Show message. Normally the card's track is slide-in and at 0 ms its transform is translateX(-40px). Switch on Reduced motion: the track is fade-in, the transform is none, and opacity still runs 0 to 1 over 300 ms (0.67 at 60 ms, in both runs). The spinner's track changes from spin to pulse.

  2. 2

    Make panel B safe too

    In "The reset that breaks transitionend", change closeNaive(b) to closeSafe(b). The second line becomes reset, naive code: removed (the label is fixed text): the duration reads 0s, so the panel is removed at once.

Code & diagrams

Reduce, don't remove

The line under the spinner prints the computed animation names. Press Show message, then switch on the timeline's Reduced motion: `slide-in` becomes `fade-in` and `spin` becomes `pulse`. The text is the same.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.
The reset that breaks transitionend

Panels A and B wait for `transitionend`. Panel B has the blanket reset's `transition: none`, so the event never comes and it stays. Panel C checks the transition duration first.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

normal, naive code: removed
reset, naive code: still in the page
reset, safe code: removed

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Reverse the query

In "Reduce, don't remove", change @media (prefers-reduced-motion: reduce) to @media (prefers-reduced-motion: no-preference) and switch on Reduced motion.

terminal
$ Run as a reduced-motion user and press Show message
── what you'll see ──
The card's track says `slide-in` and the spinner's says `spin`: the people who asked for calm get all the movement, and everyone else gets the calm version.

Myth vs fact

Myth

Reduced motion means no animation.

Fact

It means less movement. Fades, colour changes and short state changes are welcome, and they carry the message that something changed.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    prefers-reduced-motion only answers for the device. A visitor may want calm on your site only, or full motion despite the device setting. Topic 7.5 adds a site switch that sits above this query.

Remember this

  1. 1

    The problem this solves: Topic 7.1 listed what makes people ill, and every phone and laptop has a switch where a person says "I need less movement". Your page does not know about that switch unless your CSS asks. A media query is a condition that wraps CSS rules, and prefers-reduced-motion is the one that reads this setting.

  2. 2

    The two values. no-preference means the visitor has not asked for anything. reduce means they switched the setting on. MDN notes that @media (prefers-reduced-motion) with no value means reduce, and that the feature is Baseline widely available. The timeline's "Reduced motion" switch re-runs an example with reduce answering true, so you can test without touching your device.

  3. 3

    Reduce, don't remove. The easy fix is to switch every animation off. That throws away the job the motion did: the toast no longer says "this just appeared", the panel no longer shows where it came from. The better fix keeps the change visible and removes the travel: a slide becomes a short fade, a spin becomes a gentle pulse, a bounce becomes nothing. Colour, opacity and size-free changes are the safe tools (Topic 7.1).

  4. 4

    Two ways to write the rule. The opt-out pattern gives everyone the motion and a reduce block takes it away: the toast in Phase 4 swaps its animation names this way. The opt-in pattern writes the movement only inside no-preference: the press scale in Phase 3 works this way. Opt-in is safer for big movement, because anything you forget to list stays calm. Use opt-out for small swaps, where the base rule is already a fade.

  5. 5

    The blanket reset trap. A popular snippet puts * { animation: none !important; transition: none !important; } in the reduce block. Code that waits for transitionend or animationend then waits forever, because nothing ran, and the panel is never removed. Ask whether a transition will run before waiting (as closeSafe does), or use a tiny duration such as 0.01ms, which still fires the events (measured here).

  6. 6

    Essential animation. Some motion is information, such as progress. Keep it, calmer: the spinner below pulses its opacity instead of spinning, and the words "Loading the Volt…" stay. WCAG counts motion as essential only when nothing else could give the same information (Topic 7.4).

  7. 7

    Where people turn it on (MDN's list): Windows 11 Settings, Accessibility, Visual Effects, Animation effects; macOS Settings, Accessibility, Display, Reduce motion; iOS Settings, Accessibility, Motion; Android 9+ Settings, Accessibility, Remove animations; GNOME Settings, Accessibility, Seeing, Reduced animation. Menu names change between versions. Chrome DevTools' Rendering tab can emulate it.

Explain it without notes

01

What is the difference between reduce and no-preference?

02

Why is "reduce, don't remove" better than switching all animation off?

Practice

01

Write the rule that makes .spinner pulse instead of spin for reduced-motion users.

02

Write a rule that gives a dialog a slide-and-fade entrance for everyone and only a fade for reduced-motion users, using the opt-out pattern.

Done when you can

  • You can read both values and say who each matches

  • You swap movement for a fade instead of deleting the animation

  • You know why a blanket reset breaks transitionend code

  • You can say where users switch the setting on