Respecting Reduced Motion Preferences | Blue Frog Docs

Respecting Reduced Motion Preferences

Diagnose and fix motion animation issues that cause problems for users with vestibular disorders and motion sensitivity

Respecting Reduced Motion Preferences

What This Means

The prefers-reduced-motion media query allows users to indicate they prefer minimal animation and motion on web pages. Users with vestibular disorders, motion sickness, or attention disorders can experience nausea, dizziness, or seizures from excessive animations, parallax effects, and auto-playing videos.

Common Motion Issues

Problematic animations:

  • Parallax scrolling effects
  • Auto-scrolling carousels
  • Large moving backgrounds
  • Spinning/rotating elements
  • Bouncing or shaking animations
  • Sudden movements or zooms
  • Flickering or strobing effects

User Preference:

  • Users can enable "Reduce Motion" in their OS
  • Browsers expose this via prefers-reduced-motion media query
  • Websites should respect this preference

Impact on Your Business

Accessibility Impact:

  • WCAG 2.1 Level AAA (2.3.3 Animation from Interactions)
  • WCAG 2.2 Level AAA (2.3.2 Three Flashes)
  • Users with vestibular disorders get physically ill
  • Can trigger seizures in epileptic users
  • Causes disorientation and nausea
  • Creates anxiety for sensitive users

Legal Compliance:

  • Required for Level AAA compliance
  • Increasingly expected for Level AA
  • Common in accessibility lawsuits
  • Some jurisdictions requiring it

User Impact:

  • 35% of adults experience motion sickness
  • 15-35% of people have vestibular disorders
  • Growing awareness and adoption of reduced motion
  • Affects productivity and comfort

Business Consequences:

  • Physical illness forces users to leave site
  • Negative reviews and complaints
  • Lost conversions from affected users
  • Brand damage from ignoring accessibility

How to Diagnose

Method 1: Enable Reduced Motion on Your System

macOS:

  1. System Preferences → Accessibility
  2. Display → Reduce motion
  3. Check the box

Windows 10/11:

  1. Settings → Ease of Access → Display
  2. Show animations in Windows → Off

iOS:

  1. Settings → Accessibility
  2. Motion → Reduce Motion → On

Android:

  1. Settings → Accessibility
  2. Remove animations → On

After enabling, test your site:

  • Animations should be reduced or removed
  • Parallax effects should be static
  • Auto-playing videos should pause
  • Transitions should be instant or minimal

Method 2: Test with DevTools

Chrome DevTools:

  1. Open DevTools (F12)
  2. Press Cmd/Ctrl + Shift + P
  3. Type "Rendering"
  4. Select "Show Rendering"
  5. Find "Emulate CSS media feature prefers-reduced-motion"
  6. Select "prefers-reduced-motion: reduce"

Firefox DevTools:

  1. Open DevTools (F12)
  2. Click three dots menu
  3. Settings → Advanced
  4. Check "prefers-reduced-motion: reduce"

What to Look For:

  • Animations should stop or minimize
  • Parallax effects should disappear
  • Transitions should be instant
  • Auto-play should be disabled

Method 3: Use Accessibility Audit Tools

axe DevTools:

  1. Install axe DevTools Extension
  2. Run scan
  3. Look for motion-related issues

Manual code review:

/* Search your CSS for animations without reduced motion support */
@keyframes slideIn { }
.parallax { transform: translateY() }
transition: all 0.5s;

Method 4: JavaScript Detection

Check in browser console:

// Check if user prefers reduced motion
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

console.log('User prefers reduced motion:', prefersReducedMotion);

// Listen for changes
window.matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', (e) => {
  console.log('Motion preference changed:', e.matches);
});

Method 5: Manual Visual Testing

  1. Enable reduced motion on your device
  2. Navigate through your site
  3. Look for:
    • Animations that still play
    • Parallax effects still active
    • Auto-scrolling elements
    • Flickering or flashing
    • Spinning elements

General Fixes

Fix 1: Disable Animations for Reduced Motion

Basic implementation:

/* Define animations normally */
.fade-in {
  animation: fadeIn 0.5s ease-in;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Disable for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  .fade-in {
    animation: none;
  }
}

Better approach - respect motion preference globally:

/* Disable ALL animations and transitions */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Selective approach (recommended):

/* Keep safe animations, remove problematic ones */
@media (prefers-reduced-motion: reduce) {
  /* Remove large movements */
  .parallax,
  .slide-in,
  .zoom-effect {
    animation: none !important;
    transform: none !important;
  }

  /* Keep subtle fades (safe) */
  .fade {
    animation-duration: 0.1s;
  }

  /* Instant transitions */
  .transition {
    transition-duration: 0.01ms;
  }

  /* Disable auto-scroll */
  .carousel {
    scroll-behavior: auto;
  }
}

Fix 2: Handle Parallax Effects

Disable parallax scrolling:

/* Parallax effect */
.parallax-bg {
  background-attachment: fixed;
  background-position: center;
  transform: translateY(var(--scroll-position));
}

/* Disable for reduced motion */
@media (prefers-reduced-motion: reduce) {
  .parallax-bg {
    background-attachment: scroll;
    transform: none;
  }
}
// JavaScript parallax
const parallaxElements = document.querySelectorAll('.parallax');

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

window.addEventListener('scroll', () => {
  // Don't apply parallax if user prefers reduced motion
  if (prefersReducedMotion) return;

  parallaxElements.forEach(el => {
    const scrolled = window.pageYOffset;
    el.style.transform = `translateY(${scrolled * 0.5}px)`;
  });
});

Fix 3: Respect Motion in JavaScript Animations

Check preference before animating:

// Utility function to check motion preference
function shouldReduceMotion() {
  return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}

// Example: Smooth scroll
function scrollToSection(target) {
  if (shouldReduceMotion()) {
    // Jump instantly
    target.scrollIntoView({ behavior: 'auto' });
  } else {
    // Smooth animation
    target.scrollIntoView({ behavior: 'smooth' });
  }
}

// Example: Carousel auto-advance
function setupCarousel() {
  const carousel = document.querySelector('.carousel');

  if (!shouldReduceMotion()) {
    // Only auto-advance if motion is allowed
    setInterval(() => {
      advanceCarousel();
    }, 5000);
  }
  // Still allow manual navigation
}

// Example: Animated counter
function animateCounter(element, target) {
  if (shouldReducedMotion()) {
    // Show final value immediately
    element.textContent = target;
    return;
  }

  // Animate count-up
  let current = 0;
  const increment = target / 100;
  const timer = setInterval(() => {
    current += increment;
    if (current >= target) {
      element.textContent = target;
      clearInterval(timer);
    } else {
      element.textContent = Math.floor(current);
    }
  }, 20);
}

Fix 4: Handle Video and GIF Animations

Pause auto-playing animations:

<video
  id="background-video"
  autoplay
  muted
  loop
  playsinline
>
  <source src="background.mp4" type="video/mp4">
</video>

<script>
// Pause video if user prefers reduced motion
const video = document.getElementById('background-video');
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');

function handleMotionPreference(e) {
  if (e.matches) {
    video.pause();
  } else {
    video.play();
  }
}

// Check initial preference
handleMotionPreference(prefersReducedMotion);

// Listen for changes
prefersReducedMotion.addEventListener('change', handleMotionPreference);
</script>

Replace animated GIFs with static images:

<picture>
  <!-- Static image for reduced motion -->
  <source
    srcset="image-static.jpg"
    media="(prefers-reduced-motion: reduce)"
  >
  <!-- Animated GIF for normal motion -->
  <img src="image-animated.gif" alt="Description">
</picture>

Fix 5: Create Motion-Safe Alternatives

Design with motion preference in mind:

/* Default: subtle animation */
.button {
  transition: background-color 0.2s, transform 0.2s;
}

.button:hover {
  transform: scale(1.05);
}

/* Reduced motion: instant color change, no transform */
@media (prefers-reduced-motion: reduce) {
  .button {
    transition: background-color 0.01ms;
  }

  .button:hover {
    transform: none;
  }
}

Progressive enhancement:

/* Base styles - no motion */
.card {
  opacity: 1;
  transform: none;
}

/* Add motion only if user allows */
@media (prefers-reduced-motion: no-preference) {
  .card {
    opacity: 0;
    transition: opacity 0.5s, transform 0.5s;
  }

  .card.visible {
    opacity: 1;
    transform: translateY(0);
  }
}

Fix 6: Implement CSS Custom Properties

Make animation duration controllable:

:root {
  --animation-duration: 0.3s;
  --transition-duration: 0.2s;
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --animation-duration: 0.01ms;
    --transition-duration: 0.01ms;
  }
}

/* Use variables throughout */
.element {
  transition: all var(--transition-duration);
  animation-duration: var(--animation-duration);
}

Fix 7: Provide User Controls

Let users override motion settings:

<div class="motion-controls">
  <label>
    <input
      type="checkbox"
      id="reduce-motion"
      aria-label="Reduce motion and animations"
    >
    Reduce motion
  </label>
</div>

<script>
const checkbox = document.getElementById('reduce-motion');
const body = document.body;

// Check system preference
const systemPrefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

// Load saved preference or use system preference
const savedPreference = localStorage.getItem('reduceMotion');
const shouldReduce = savedPreference !== null
  ? savedPreference === 'true'
  : systemPrefersReduced;

// Set initial state
checkbox.checked = shouldReduce;
if (shouldReduce) {
  body.classList.add('reduce-motion');
}

// Handle user changes
checkbox.addEventListener('change', (e) => {
  if (e.target.checked) {
    body.classList.add('reduce-motion');
    localStorage.setItem('reduceMotion', 'true');
  } else {
    body.classList.remove('reduce-motion');
    localStorage.setItem('reduceMotion', 'false');
  }
});
</script>

<style>
/* Apply reduced motion when class present */
.reduce-motion *,
.reduce-motion *::before,
.reduce-motion *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}
</style>

Platform-Specific Guides

Detailed implementation instructions for your specific platform:

Platform Troubleshooting Guide
Shopify Shopify Motion Preference Guide
WordPress WordPress Motion Preference Guide
Wix Wix Motion Preference Guide
Squarespace Squarespace Motion Preference Guide
Webflow Webflow Motion Preference Guide

Verification

After implementing reduced motion support:

  1. Enable reduced motion:

    • Turn on in your OS settings
    • Verify animations stop/minimize
    • Check all page types
  2. Test with DevTools:

    • Use "Emulate prefers-reduced-motion"
    • Toggle between reduce/no-preference
    • Verify behavior changes
  3. Test all animations:

    • Parallax effects
    • Carousels
    • Transitions
    • Hover effects
    • Page load animations
  4. Check JavaScript:

    • Auto-scroll features
    • Animated counters
    • Video autoplay
    • Smooth scroll
  5. Real device testing:

    • Test on iPhone with Reduce Motion on
    • Test on Android
    • Verify no nausea-inducing effects

Common Mistakes

  1. Ignoring prefers-reduced-motion - Not checking at all
  2. Only fixing some animations - Need to check all motion
  3. Making site non-functional - Some users need to see changes
  4. Forgetting JavaScript animations - CSS isn't the only source
  5. Not testing with real users - Hard to know what's problematic
  6. Removing all feedback - Keep instant visual feedback
  7. Parallax everywhere - Very problematic for vestibular disorders
  8. Auto-scrolling carousels - Respect motion preference
  9. Flashing/strobing effects - Can trigger seizures
  10. Not providing controls - Let users decide

Safe Animations

Animations that are generally safe:

  • Fade in/out (opacity only)
  • Color changes
  • Static position changes
  • Instant state changes

Problematic animations to avoid:

  • Large movements (sliding, bouncing)
  • Rotation/spinning
  • Scaling (especially large scale)
  • Parallax scrolling
  • Auto-scrolling
  • Zooming effects
  • Shaking/vibrating

Additional Resources

// SYS.FOOTER