How to Respect prefers-reduced-motion in SVG and UI Animation
Respect prefers-reduced-motion by making the static state your baseline, then adding non-essential movement only for no-preference. When motion carries meaning, preserve that meaning with a calmer effect, visible status, or user control.
The short answer: make the complete, static interface your baseline. Add non-essential motion only inside (prefers-reduced-motion: no-preference). For reduce, remove or replace large translations, zooms, spins, parallax, and autoplay loops while preserving the same state, feedback, and task. You do not have to remove every visual transition: a brief cross-fade, color change, or restrained essential effect may be the clearer alternative.
prefers-reduced-motion reflects a preference the user has expressed through their device or browser. It is a design input, not a complete accessibility policy. W3C advises supporting motion preferences and eliminating unnecessary interaction motion, while its separate Pause, Stop, Hide criterion covers certain automatically moving content. Treat the query as one layer alongside sound motion design, accessible alternatives, and user controls.
Understand reduce and no-preference
The media feature has two values. reduce means the user has asked the system to minimize non-essential motion. no-preference means no preference has been made known; it does not mean “please animate” or prove that motion is comfortable for this user. The Media Queries Level 5 specification defines both values, and MDN’s prefers-reduced-motion reference notes that reduce evaluates as true in a boolean query.
That makes @media (prefers-reduced-motion) equivalent to an explicit reduce query, but spelling out the value is easier to review. A cautious production default is static-first: render the meaningful end state without depending on animation, then opt into stronger motion only for no-preference.
Use this production decision tree
- Can the task and meaning survive with no animation? If yes, treat the motion as enhancement. Show the final state by default and skip the effect for reduce.
- Does motion communicate feedback or a state change? Preserve the message, not necessarily the movement. Replace a slide or zoom with an instant update, short cross-fade, visible status, step label, or progress value.
- Is movement genuinely essential? Keep the least intense version that still conveys the information. Reduce distance, scale, speed, repetition, and viewport coverage; add play, pause, or replay control where practical. “Essential” is a narrow exception: removing it would fundamentally change information or functionality.
- Does content start automatically and move for more than five seconds beside other content? Provide a way to pause, stop, or hide it unless it is essential. That is part of WCAG 2.2 Success Criterion 2.2.2 (Level A), regardless of this media query.
- What starts the animation? Gate CSS in CSS. Gate Web Animations API, requestAnimationFrame(), and library timelines in JavaScript. A CSS override cannot stop work created by JavaScript.
- Can the preference change while the page is open? CSS media queries update automatically. Scripted systems need a change listener or a library integration that rebuilds and cleans up animations.
W3C’s explanation of WCAG 2.2 Success Criterion 2.3.3, Animation from Interactions (Level AAA), specifically identifies unnecessary parallax and interaction-triggered motion as concerns, while allowing essential animation. The goal is controlled, equivalent communication—not a blanket ban on motion.
Make CSS animation an opt-in enhancement
Put the finished state in ordinary CSS. Place only the moving keyframes and animation assignment inside no-preference:
.confirmation-mark {
opacity: 1;
transform: none;
}
@media (prefers-reduced-motion: no-preference) {
.confirmation-mark {
animation: confirmation-enter 480ms ease-out both;
}
}
@keyframes confirmation-enter {
from {
opacity: 0;
transform: translateY(20px) scale(.96);
}
to {
opacity: 1;
transform: none;
}
}If the query does not match, the mark is already visible and usable. This positive opt-in pattern follows the alternative shown in W3C technique C39. It also avoids the fragile global hack that forces every animation to a near-zero duration. The web.dev implementation guide warns that blanket overrides can break flows that depend on animation completion and cannot stop Web Animations API work; they can also leave infinite animations running.
Use the same structure when adapting the mask-based example in Building a Logo Reveal with SVG: the full logo should be the baseline, and the reveal should be optional.
Handle Web Animations API motion and live changes
For scripted animation, query the same feature with matchMedia(), cancel existing work before rebuilding, and listen for changes. This example assumes the base CSS already displays .hero-mark in its final position:
const mark = document.querySelector(".hero-mark");
if (mark) {
const reducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
);
let entrance = null;
function syncMotion() {
entrance?.cancel();
entrance = null;
if (reducedMotion.matches) {
return;
}
entrance = mark.animate(
[
{ opacity: 0, transform: "translateY(24px) scale(.96)" },
{ opacity: 1, transform: "none" }
],
{
duration: 500,
easing: "cubic-bezier(.2, .8, .2, 1)"
}
);
}
syncMotion();
reducedMotion.addEventListener("change", syncMotion);
}MDN documents both the initial matches check and monitoring a MediaQueryList for changes. Its Animation.cancel() reference explains that cancellation removes the animation’s effects and aborts playback, so the underlying static style must be correct.
In a component or single-page application, remove the listener when the component unmounts. Also avoid making business logic depend solely on an animation finishing: cancellation rejects the animation’s finished promise. Complete the state change independently, and let animation illustrate the result.
Respect the preference in GSAP timelines
GSAP 3.11 and later provides gsap.matchMedia(). Create full-motion timelines only when no-preference matches:
const motion = gsap.matchMedia();
motion.add(
"(prefers-reduced-motion: no-preference)",
() => {
const timeline = gsap.timeline();
timeline.from(".diagram-part", {
y: 24,
opacity: 0,
stagger: 0.08,
duration: 0.45,
ease: "power2.out"
});
}
);The official GSAP documentation says animations and ScrollTriggers created in the matching context are collected and reverted when the condition stops matching. Return a cleanup function for non-GSAP listeners or other side effects, and call motion.revert() when the owning component is destroyed. Keep the static styles outside the timeline so reverting exposes a complete interface.
If you are still choosing an animation system, CSS vs GSAP for SVG explains the maintenance trade-offs. The accessibility requirement is the same either way: every engine that creates motion needs an explicit reduced-motion path.
Provide accessible alternatives, not empty space
A reduced-motion experience must retain purpose. For a decorative loop, show the finished artwork. For navigation or disclosure, update the selected or expanded state instantly. For progress, expose a percentage or status text. For an instructional animation, offer a static sequence, transcript, diagram, or user-initiated playback. Do not make color the only signal.
This pattern keeps an SVG checkmark decorative and announces the actual result as text, whether or not the drawing animation runs:
<div role="status">
<svg
class="confirmation-mark"
aria-hidden="true"
focusable="false"
viewBox="0 0 24 24">
<path d="M5 12l4 4L19 6" fill="none" stroke="currentColor" />
</svg>
<span>Upload complete.</span>
</div>If the SVG itself conveys information, give it an appropriate accessible name and description instead. The decision patterns in Accessible SVG: title, desc, and aria-hidden cover informative, interactive, and decorative graphics in detail.
Reduced motion does not always mean zero animation. A short opacity transition can preserve continuity without moving content through space. Essential previews—such as motion inside an animation editor—may remain, but should be user-initiated or controllable and accompanied by a non-motion explanation wherever one can convey the same result.
Test reduced motion before release
- Inventory every motion source. Search for CSS animations and transitions, SVG animation, Web Animations API calls, animation-frame loops, GSAP timelines, ScrollTrigger, animated images, and autoplay media.
- Test both values from a cold load. Enable the real operating-system setting, reload, and confirm the first rendered state is complete. Then test no-preference. Browser emulation is useful, but it should not be the only check.
- Exercise every trigger. Check load, hover, keyboard focus, activation, scrolling, route changes, dialogs, validation, loading, success, and error states.
- Change the preference without reloading. CSS should respond immediately. Verify that Web Animations are canceled, GSAP contexts revert, and newly allowed motion initializes only once.
- Verify equivalent meaning and operation. No control may disappear, remain off-screen, or wait forever for an animation callback. Check visible status text, focus order, keyboard operation, and pause controls.
- Profile a realistic page. Record both variants on representative mobile hardware. Confirm reduced mode actually stops continuous script and rendering work rather than merely hiding it.
- Document exceptions. For each motion effect retained under reduce, record why it is essential, how intensity was minimized, what control exists, and what alternative communicates the result.
These checks extend the procedures in W3C techniques C39 for CSS and SCR40 for JavaScript.
Reduced motion can improve performance—if work stops
Skipping a decorative timeline can reduce style calculations, paints, compositing, and JavaScript callbacks. Hiding an animated element with opacity does not guarantee those costs disappear: cancel the Web Animation, stop the animation-frame loop, and revert or kill the GSAP work. Static-first CSS is especially efficient because the unnecessary animation is never created for reduce.
Performance and motion safety are different decisions. transform and opacity are usually cheaper to render than geometry-changing properties, but a smooth full-screen zoom can still cause discomfort. MDN’s animation performance guide explains the rendering cost, while SVG animation performance best practices provides a focused production checklist.
Quick answers
Does no-preference mean the user wants animation?
No. It only means no reduced-motion preference is known. Keep default motion purposeful and restrained even when the query matches.
Should prefers-reduced-motion disable every animation?
No. Remove non-essential spatial motion aggressively, but preserve meaning. A brief cross-fade, instant state change, or minimized essential effect may be better than removing feedback altogether.
Is prefers-reduced-motion enough for WCAG compliance?
No. It can support WCAG techniques for interaction-triggered motion, but autoplaying movement may also need pause, stop, or hide controls. Keyboard access, alternatives, flashing limits, and other requirements still apply.
Will JavaScript notice a preference change automatically?
The MediaQueryList.matches value changes, but your animation code must listen for the change event or use an integration such as gsap.matchMedia() to rebuild and clean up behavior.
What is the safest default for an animated SVG?
Render a complete, accessible static state first. Add the animation as an enhancement, ensure reduced mode retains the same message and controls, and test both preferences on a real page.
Sources and further reading
- Media Queries Level 5: prefers-reduced-motion
- prefers-reduced-motion CSS media feature
- Understanding SC 2.3.3: Animation from Interactions
- Technique C39: Using the CSS prefers-reduced-motion query to prevent motion
- Technique SCR40: Using the CSS prefers-reduced-motion query in JavaScript
- Understanding SC 2.2.2: Pause, Stop, Hide
- Window: matchMedia() method
- Animation: cancel() method
- gsap.matchMedia()
- Animation performance and frame rate
- prefers-reduced-motion: Sometimes less movement is more
Related articles
SVG Icon Sprites in Production: Accessibility, IDs, and Animation
Build production SVG icon sprites with symbol and use. Prevent ID collisions, style and animate safely, handle accessibility, caching, CORS, and testing.