Skip to content

Accessibility

prefers-reduced-motion — Velocity X's Animation Pause Pattern That Respects User Preferences

CSS @media Block + JS matchMedia — Vestibular-Friendly, WCAG Compliant

⏸️ 🧠
Velocity X has heavy animation: fullscreen hero sliders, GSAP scroll-triggered reveals, WebGL shader playgrounds cycling through colourscapes. Beautiful. But here's the thing: ~30% of users have 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.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.