prefers-reduced-motion: reduce enabled in their OS settings. For them, animation causes vertigo, nausea, or sensory overload. Velocity X doesn't ignore them—it respects the signal. CSS freezes keyframes. JavaScript reads window.matchMedia and skips animation init entirely. No jank, no reduced-motion users feeling sick on your site. WCAG 2.1 Level AA mandates it. It's also just kind.
Why prefers-reduced-motion Matters
Vestibular disorders, migraines, ADHD, autism, and general motion sensitivity affect 1 in 3 Australians. When a website loops parallax backgrounds or animates large blocks of the page, these users feel it physically. Nausea. Disorientation. Bounce rate: instant. Legally, WCAG 2.1 Success Criterion 2.3.3 (Animation from Interactions) says: if something animates for more than 5 seconds, users must be able to pause it, OR you must respect prefers-reduced-motion. Velocity X chooses respect—no pause buttons needed. The OS preference silently kills the animation at render time.
The CSS Pattern
Every GSAP animation, keyframe, and CSS transition in Velocity X sits inside a media query wrapper:
/* Normal users see the full animation */
@keyframes slideIn {
from { opacity: 0; transform: translateX(-100%); }
to { opacity: 1; transform: translateX(0); }
}
.hero-slider {
animation: slideIn 0.8s ease-out;
}
/* Users with prefers-reduced-motion see instant state—no animation */
@media (prefers-reduced-motion: reduce) {
@keyframes slideIn {
from { opacity: 1; transform: translateX(0); }
to { opacity: 1; transform: translateX(0); }
}
.hero-slider {
animation: none;
}
}
The trick: the @media block redefines the animation to be instant. No animation property at all, or animation-duration: 0s. Content still renders, layout doesn't break, but movement stops cold.
The JavaScript Pattern
GSAP scroll triggers and dynamic animations live in React components. Velocity X checks prefers-reduced-motion at init:
import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';
const HeroSlider = () => {
useEffect(() => {
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReduced) {
// Skip animation entirely; show final state
gsap.set('.slider-item', { opacity: 1, x: 0 });
return;
}
// Normal: GSAP scroll trigger + parallax
gsap.registerPlugin(ScrollTrigger);
gsap.to('.slider-item', {
scrollTrigger: {
trigger: '.slider',
start: 'top center',
end: 'bottom center',
scrub: 1,
},
x: -100,
opacity: 1,
});
}, []);
return <div className="slider">...</div>;
};
On mount, check the preference. If reduce is on, set final state and skip GSAP. If not, animate normally. Clean, no flicker.
Testing prefers-reduced-motion
Chrome/Edge DevTools: Open Console, run window.matchMedia('(prefers-reduced-motion: reduce)').matches. If true, your OS has it on. To toggle: macOS System Preferences → Accessibility → Display → Reduce Motion. Windows: Settings → Ease of Access → Display → Show animations.
Browser DevTools simulation: Chrome DevTools → Rendering → Emulate CSS media feature prefers-reduced-motion. Force it on without changing OS settings. Reload. Watch animations vanish. That's what 30% of your users see every day.
Automated testing: Playwright can emulate it: browser.newContext({ reducedMotion: 'reduce' }). Screenshot the page, compare to normal mode. Different? That's a win—you're respecting the signal.
Six FAQs
Does every animation need @media wrapping?
Yes. If it moves for more than 200ms, wrap it. Decorative micro-interactions (icon hovers, loader spins) can skip it if they're under 5 seconds and don't fill the viewport. But hero sliders, scroll reveals, background parallax? Always wrap.
What if I have a WebGL shader with motion?
Shaders live in canvas. CSS @media can't reach them. Instead, check matchMedia in the Three.js or Babylon.js setup. If reduce is on, either skip the shader entirely or render a static final frame. Velocity X does this in shader playgrounds—users with reduce see a still colour block instead of an animated gradient.
Does prefers-reduced-motion affect performance?
No—it improves it. Skipping GSAP scroll triggers and keyframe animations means less GPU/CPU work. Vestibular-friendly users often see faster page load too.
Can I add a "skip animations" toggle instead?
Don't. Respect OS preference. Adding a toggle means users have to find it, toggle it, reload. Most won't. The OS setting is already there—use it. Toggles are friction.
What if my animation is essential to understanding the feature?
Rethink the design. Animation shouldn't carry meaning. If it does, add static text or icons so users with reduce can understand without the motion. Example: a slider animates left. Add arrows on both sides. Users with reduce understand "swipe left to see more" without the actual swipe animation.
Is prefers-reduced-motion WCAG mandatory?
Yes. WCAG 2.1 Success Criterion 2.3.3 + Australian Disability Discrimination Act = required. Velocity X ships compliant; you inherit the pattern.
The Bottom Line
Animation is seductive. Velocity X ships heavy animation because motion is premium. But animation without respect is ableist—it silently excludes 30% of users. Velocity X wraps every animation in @media (prefers-reduced-motion: reduce) and checks matchMedia before initializing GSAP. Vestibular-friendly users see instant state changes. Everyone else sees the full magic. WCAG compliant, accessible, and genuinely kind. See Velocity X pricing to build animation with conscience, or dive deeper into WCAG 2.2 AA accessibility defaults that ship out of the box.