prefers-reduced-motion: accessible motion without killing the design
The spec's word is reduce, not remove. Why animation: none !important gets it wrong on both sides, and the substitution table that preserves the design.
- accessibility
- motion design

Contents
The spec's word is reduce, not remove.
MDN lists three ways out: the interface can remove, reduce or replace the animation. The third is the one almost nobody implements.
The recipe in circulation is throwing animation: none !important at everything. It gets it wrong on both sides.
It erases what communicates state, such as the spinner saying the system is working. And it touches the JavaScript parallax in no way, which is the real cause of the problem.
What causes the harm
Before deciding what to switch off, knowing what you are avoiding pays off, because the answer changes the target.
The documented purpose of the preference is serving people with vestibular disorders of the inner ear. The W3C describes the reactions as severe: dizziness, nausea, migraine, loss of focus, and cases where the person needs to lie down to recover. MDN is specific about the trigger: scaling or displacing large objects provokes discomfort.
Out of that comes a practical criterion that appears in few tutorials:
| Pattern | Risk | Why |
|---|---|---|
| Parallax and background movement | High | A large area displacing out of sync with the gesture |
| Zoom and scaling of a large element | High | It simulates approach and fools the vestibular system |
| Continuous rotation and spin | High | Movement with no point of rest |
| Sliding a whole section | Medium | Depends on the area and the distance |
| Small displacement, a few pixels | Low | Small area, short duration |
| Opacity fade | Low | No movement, only a change in luminance |
| Color change | Low | No spatial component |
The last row is the key to the whole post. If opacity and color are safe, delivering a static page to whoever turned the preference on is unnecessary. You deliver the same page with a different transition vocabulary.
The syntax, and the choice it forces
Two values, and the difference between them is asymmetric.
/* the person turned the preference on in their system */
@media (prefers-reduced-motion: reduce) { }
/* nobody declared a preference; evaluates as false when reduce is on */
@media (prefers-reduced-motion: no-preference) { }
/* short form, equivalent to reduce */
@media (prefers-reduced-motion) { }That forces an architecture choice you can invert:
Opt-out pattern. You animate by default and switch off inside reduce. It is the most common and the most fragile: in a browser without support for the query, the switch-off block never runs and the animation plays.
Opt-in pattern. You write the whole animation inside no-preference. The no-animation state becomes the default, and movement is the addition.
.card {
opacity: 0;
transform: translateY(24px);
}
/* with no query support, this block never applies and nothing animates */
@media (prefers-reduced-motion: no-preference) {
.card {
transition: opacity .5s ease, transform .5s ease;
}
.card.visible { opacity: 1; transform: none; }
}One caution about the example above. The initial state hides the element, so the reveal depends on the animation running. Without it, the content disappears.
The safe way inverts that: the final state is the default, and the animation decorates the arrival alone. It is never what makes the content visible.
Why animation: none !important is the wrong answer
The short recipe in circulation:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
}Three concrete problems, in order of severity.
It erases animation carrying information. A loading spinner, a progress bar, a recording indicator: all of them communicate state through movement. Switched off, the person has no way to know whether the system froze. WCAG criteria 2.3.3 and 2.2.2 both carve out an explicit exception for animation essential to the functionality or the information conveyed, and that exception exists for this reason.
It never reaches what hurts most. Parallax built with transform in JavaScript, scroll animation with ScrollTrigger, an autoplaying background video: none of that is CSS animation or transition. The universal selector passes far from the real problem.
It delivers a worse experience for nothing. The person asked for less movement, not less design. Zeroing every transition makes the interface respond in jolts, which worsens the perception of quality with no accessibility gain.
A less bad variant exists. It swaps none for a minimal duration instead of cutting. That way the animation end events keep firing, and JavaScript depending on them survives:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
}
}It is still emergency medicine for a legacy site. For new code, replace instead of cutting.
Replace, do not remove
The table I use as a project reference:
| Original effect | Substitution under reduce |
|---|---|
| Entrance sliding up from below | Opacity fade, in place |
| Background parallax | Static background, no displacement |
| Image zoom on hover | Change of brightness or border |
| Auto-advancing carousel | No auto-advance, manual control only |
| Animated counter | The final value, straight away |
| Sliding page transition | Hard cut or short fade |
| Autoplaying background video | The first frame as a static image |
| Smooth scrolling | A direct jump to the destination |
The principle: preserve the meaning, swap the vehicle. If the slide upward said "this is new", the fade says the same thing without displacing a large area.
The JavaScript side
CSS reaches less than everything. For animation in code, the same query works through matchMedia, and the change event matters more than it looks:
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
function apply() {
if (query.matches) {
stopParallax();
document.querySelectorAll("video[autoplay]").forEach(v => v.pause());
} else {
startParallax();
}
}
apply();
query.addEventListener("change", apply);Reading query.matches once, on load, is the most common error. The person can turn the preference on mid-session, and without the change listener the page keeps moving until a reload.
In the two libraries that dominate the market, the idiom already exists.
GSAP treats the preference as one more condition in gsap.matchMedia(). It reverts on its own whatever got created inside, animation and ScrollTrigger alike:
const mm = gsap.matchMedia();
mm.add({
reduced: "(prefers-reduced-motion: reduce)",
normal: "(prefers-reduced-motion: no-preference)",
}, (ctx) => {
const { reduced } = ctx.conditions;
gsap.from(".section", {
opacity: 0,
y: reduced ? 0 : 60,
duration: reduced ? 0.2 : 0.8,
scrollTrigger: { trigger: ".section", start: "top 85%" },
});
});Motion, the former Framer Motion, solves it at the application level with MotionConfig. With reducedMotion="user", it disables transform and layout animations and keeps opacity and background color, which is the recommended substitution, to the letter:
<MotionConfig reducedMotion="user">
<App />
</MotionConfig>The accepted values are "user", "always" and "never". For fine control, the useReducedMotion() hook returns a boolean and lets you choose the substitution case by case, such as swapping x: "-100%" for opacity: 0 in a side drawer.
The eight ScrollTrigger patterns, that responsive slice included, sit in ScrollTrigger: the 8 patterns.
Four points almost every site forgets
Smooth scrolling. scroll-behavior: smooth is the most frequent omission, and the documentation promises no automatic handling: MDN records only that the browser may ignore the property. Count on nothing there, declare the intent:
@media (prefers-reduced-motion: no-preference) {
html { scroll-behavior: smooth; }
}Autoplaying background video. It is neither animation nor transition, so no CSS reset reaches it. It needs JavaScript, or swapping the video for a static poster.
Page transitions. View Transitions animate by default, and the same query switches the animation off while preserving the content swap. Their mechanics sit in View Transitions in Next.js 16.
Scroll-driven animations in CSS. They run on the compositor thread and pass through no JavaScript, which is great for performance and means the media query alone controls them. Their mechanics sit in CSS scroll-driven with no JS.
What WCAG demands, and what it does not
Separating the two criteria pays off, because the conformance level differs and that changes the priority.
| Criterion | Level | Covers |
|---|---|---|
| 2.2.2 Pause, Stop, Hide | A | Automatic movement lasting over five seconds, alongside other content |
| 2.3.3 Animation from Interactions | AAA | Motion animation triggered by interaction, such as scroll parallax |
2.2.2 is level A, the floor of conformance, and it demands a mechanism to pause, stop or hide. An auto-advancing carousel and a looping background video land here.
2.3.3, level AAA, is where parallax lives. It says animation triggered by interaction has to be disableable. The exception is the same: animation essential to the functionality or the information conveyed.
Two practical readings.
Respecting prefers-reduced-motion is the shortest path to 2.3.3. The operating system is already the disabling mechanism the criterion asks for.
2.2.2 resolves through no media query alone. It asks for a visible control in the interface, for whoever turned no preference on in their system.
How to test
Turn the preference on and reload. In a Chromium browser, emulating it through DevTools also works, under Rendering, in the option to emulate the prefers-reduced-motion media feature.
| System | Path |
|---|---|
| Windows 11 | Settings → Accessibility → Visual effects → Animation effects |
| Windows 10 | Settings → Ease of Access → Display → Show animations |
| macOS | System Settings → Accessibility → Display → Reduce motion |
| iOS | Settings → Accessibility → Motion |
| Android 9+ | Settings → Accessibility → Remove animations |
| GNOME | Settings → Accessibility → Seeing → Reduced animation |
Test the whole page with the preference on. Three questions: does all the content appear? Do the states stay legible? Did anything get stuck invisible?
The third is the most common defect among people implementing this for the first time.
The larger context, from brief to delivery, sits in the complete guide to motion design for the web. And if the doubt is which tool to use before reaching that point, the decision tree between GSAP, Motion and CSS settles it in three questions.
Frequently asked questions
What is the difference between reduce and no-preference?
reduce indicates the person turned the reduced motion setting on in their device. no-preference indicates no preference got declared, and it evaluates as false when reduce is active. Writing the animation inside no-preference leaves the motionless state as the default, in browsers without support for the query included.
Do I have to switch off every animation when the preference is active?
No. The specification speaks of removing, reducing or replacing, and animation essential to functionality or information has an explicit exception in both WCAG criteria. An opacity fade and a color change carry no spatial component and tend to be safe as substitutions.
Does scroll-behavior: smooth respect the preference on its own?
The documentation guarantees nothing there. MDN records that the browser may ignore the property, promising no reduced motion handling. Put the declaration inside @media (prefers-reduced-motion: no-preference) instead of counting on the browser's behavior.
Is this mandatory for conformance?
It depends on the level. Automatic animation lasting over five seconds falls under criterion 2.2.2, level A, which is the conformance floor and asks for a pause, stop or hide control. Animation triggered by interaction, such as parallax, falls under 2.3.3, level AAA. Respecting the system preference satisfies the second and helps with the first, replacing the interface control in no way.
Sources
- MDN Web Docs — prefers-reduced-motion. Accessed 20 August 2026.
- MDN Web Docs — scroll-behavior. Accessed 20 August 2026.
- W3C — Understanding SC 2.3.3: Animation from Interactions. Accessed 20 August 2026.
- W3C — Web Content Accessibility Guidelines 2.2. Accessed 20 August 2026.
- Motion — Accessibility and reduced motion. Accessed 20 August 2026.
- GSAP — gsap.matchMedia()/). Accessed 20 August 2026.
Verified on 20 August 2026.
Review trigger: revisit when (a) the media specification gains a value beyond reduce and no-preference, (b) browsers start documenting automatic handling of scroll-behavior under reduced motion, or (c) WCAG promotes criterion 2.3.3 to a level below AAA.
Read next
Motion •
Motion Design for the Web: The Complete Guide
Scroll, text, images and video: the complete catalog of motion techniques for the web, with implementation in Next.js and the cases where each one pays off.
- motion
- scroll
The definitive guide — a Next.js site built around motion and scroll
The scroll foundation that, when missing, keeps the animations from working at all: Lenis, GSAP and Next.js wired in the right order and the mistakes to avoid.
- next.js
- lenis
Infra •
Documentation: deploying a Next.js application with GitHub + Hostinger
Every push becomes a live site with no hosting panel involved: connecting GitHub to Hostinger, the build settings that break and the checks after each deploy.
- deploy
- github


