EdgeCases Logo
Dec 2025
CSS
Deep
7 min read

Respecting User Motion Preferences with prefers-reduced-motion

Don't just remove animations—replace motion with opacity to preserve context while respecting accessibility

css
accessibility
animation
prefers-reduced-motion
a11y
user-experience

Vestibular disorders affect millions of users, causing dizziness, nausea, or headaches when triggered by large areas of movement or parallax effects. The prefers-reduced-motion media query allows you to respect these user preferences—but "reduced" doesn't mean "none".

The "Nuclear" Option vs. The Surgical Approach

A common mistake is to globally disable all animations for users who prefer reduced motion. While effective, this can break user experience by removing context cues or, worse, breaking functionality if JavaScript relies on animationend events.

/* ❌ The Nuclear Option: Can break JS logic */
@media (prefers-reduced-motion: reduce) {
  * {
    animation: none !important;
    transition: none !important;
  }
}
Loading demo...

Instead, replace motion with opacity. This preserves the "change of state" context without the triggering movement.

/* ✅ The Surgical Approach */
@media (prefers-reduced-motion: reduce) {
  .modal {
    /* Replace slide-in with fade-in */
    animation-name: fadeIn; 
    transform: none;
  }
  
  .card {
    /* Remove hover lift, keep focus ring */
    transition: border-color 0.2s;
    transform: none;
  }
}

Handling JavaScript Animations

CSS media queries are accessible in JavaScript via window.matchMedia. Use this to disable or adjust complex JS-driven animations (like Three.js scenes or scroll-linked animations).

const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

function initAnimation() {
  if (prefersReducedMotion.matches) {
    // Render static state or simple fade
    renderStaticScene();
  } else {
    // Initialize complex motion
    initParallax();
  }
}

// Listen for changes (users can toggle this at runtime!)
prefersReducedMotion.addEventListener('change', () => {
  // Re-evaluate animation state
  updateAnimationState();
});

Testing

You don't need to change your OS settings to test this. Chrome DevTools allows you to emulate the preference:

  1. Open Command Menu (Cmd+Shift+P)
  2. Type "Show Rendering"
  3. Scroll to "Emulate CSS media feature prefers-reduced-motion"
  4. Select "prefers-reduced-motion: reduce"

Advertisement

Related Insights

Explore related edge cases and patterns

CSS
Deep
CSS Animations vs JavaScript: Layout Thrashing and the FLIP Technique
7 min
CSS
Deep
CSS Animations vs Figma: Production Reality vs Design Prototypes
7 min

Advertisement