How to Make Animations Accessible with prefers-reduced-motion: CSS and React Implementation Patterns

Wrap all animations in @media (prefers-reduced-motion: reduce) media queries to replace motion-heavy transforms with subtle opacity or color changes, ensuring users who disable motion in system settings receive accessible, non-jarring UI feedback.

Animation accessibility requires respecting the user's system-level motion preferences through the prefers-reduced-motion media query. According to the animation standards in the emilkowalski/skills repository, every animated component must check this setting before applying transforms or keyframe animations. The repository defines canonical patterns that demonstrate how to make animations accessible with prefers-reduced-motion while preserving essential visual feedback.

The Foundation: CSS Patterns for Reduced Motion

The core implementation resides in skills/review-animations/STANDARDS.md at lines 162-166, which mandates wrapping animation definitions to honor user preferences. When prefers-reduced-motion: reduce is active, eliminate movement-based transforms (translate, scale, rotate) and retain only opacity or color transitions to indicate state changes.

/* Normal animation with spatial movement */
.element {
  animation: slideIn 300ms ease-out forwards;
}

/* Reduced-motion fallback – keep only opacity/color */
@media (prefers-reduced-motion: reduce) {
  .element {
    animation: fadeIn 200ms ease;
  }
}

The slideIn keyframes might move the element across the screen, while fadeIn simply adjusts opacity. This pattern ensures users with vestibular disorders or motion sensitivity receive subtle feedback without experiencing nausea-inducing movement.

Gating Hover Effects for Pointer Precision

Beyond reduced motion, the repository emphasizes gating hover animations to prevent accidental triggers on touch devices. As specified in skills/review-animations/SKILL.md at lines 37-38, you must nest hover rules inside @media (hover: hover) and (pointer: fine) to ensure animations only fire on devices with true hover capability.

/* Hover only activates on mice/stylus, not touchscreens */
@media (hover: hover) and (pointer: fine) {
  .element:hover { 
    transform: scale(1.05); 
  }
}

This prevents mobile devices from firing "hover" events during scroll or touch interactions, eliminating unwanted motion for users navigating with coarse pointers.

React Implementation with useReducedMotion

For JavaScript-driven animations, mirror the CSS media query detection using the useReducedMotion() hook. This approach allows you to swap full sliding transitions for simple fades when the user prefers reduced motion.

import { useReducedMotion } from '@framer-motion/hooks';

function Drawer({ isOpen }) {
  const reduce = useReducedMotion();      // true if user prefers reduced motion
  const translateX = reduce ? 0 : '-100%';

  return (
    <motion.div
      animate={{ x: isOpen ? 0 : translateX }}
      transition={{ duration: reduce ? 0.2 : 0.4, ease: "easeOut" }}
    >
      {/* …content… */}
    </motion.div>
  );
}

The hook returns true when the system preference is set, allowing you to adjust spring physics, durations, or disable movement entirely while maintaining the component's functional state transitions.

Real-World Component Example

The prototype documentation in skills/prototype/PICKER.md at line 70 demonstrates production-ready implementation of these patterns. The file shows how to maintain component usability while respecting motion preferences.

@media (prefers-reduced-motion: reduce) {
  .picker { 
    animation: fade 0.2s ease; 
  }   /* keep opacity, drop translate */
}

This example illustrates the essential feedback principle: even when disabling spatial movement, the animation persists as a fade to indicate the picker activated, preventing users from missing critical state changes.

Testing and Auditing Your Implementation

To ensure compliance, reference the audit checklist in skills/improve-animations/AUDIT.md. This file flags missing prefers-reduced-motion handling during code reviews and mandates that every animated component include the media query fallback.

Key audit criteria include:

  • Interruptibility: Animations must be stoppable for rapidly-triggered UI (toggles, toasts)
  • Property constraints: Avoid animating width, height, or margin which force re-layout
  • Motion alternatives: Verify opacity or color substitutes exist for all transform animations

Summary

  • Wrap animations in @media (prefers-reduced-motion: reduce) blocks to detect system preferences
  • Replace transform-based movements with opacity or color transitions for reduced motion users
  • Gate hover effects behind @media (hover: hover) and (pointer: fine) to prevent touch device accidents
  • Use useReducedMotion() hooks in React or JavaScript frameworks for dynamic animation values
  • Audit implementations using the checklist in skills/improve-animations/AUDIT.md to ensure compliance

Frequently Asked Questions

What CSS properties should I animate when prefers-reduced-motion is enabled?

Focus on opacity and color transitions rather than spatial movement. According to skills/review-animations/STANDARDS.md, when the user prefers reduced motion, maintain essential feedback through subtle fades or color shifts while eliminating translate, scale, or rotate transforms that could trigger vestibular disorders.

How do I handle hover animations on touch devices?

Nest hover rules inside @media (hover: hover) and (pointer: fine) to prevent unwanted motion on touchscreens. As specified in skills/review-animations/SKILL.md at lines 37-38, this ensures hover effects only activate on devices with true hover capability, preventing accidental triggers during mobile scrolling.

Can I use JavaScript spring libraries with prefers-reduced-motion?

Yes, but you must check the motion preference before calculating animation values. Use the useReducedMotion() hook from libraries like Framer Motion to detect the setting, then adjust spring stiffness, duration, or disable movement entirely. Replace sliding transitions with opacity fades as shown in the emilkowalski/skills React examples.

Where should I document the reduced-motion implementation in my project?

Follow the pattern in skills/review-animations/STANDARDS.md by creating a central animation standards document that mandates prefers-reduced-motion handling for every component. Include specific code blocks showing the media query wrap and reference this documentation in your component-specific files, as demonstrated in skills/prototype/PICKER.md at line 70.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →