How to Handle prefers-reduced-motion for Accessibility in Animations

Wrap transform-based animations in @media (prefers-reduced-motion: reduce) blocks to disable motion-heavy effects while preserving essential opacity and color feedback that communicates state changes.

The emilkowalski/skills repository establishes clear standards for respecting user motion preferences in modern web interfaces. According to the animation guidelines documented across several skill files, implementing prefers-reduced-motion is not about eliminating all visual feedback but about replacing movement-heavy effects with safer alternatives that prevent vestibular discomfort.

Core Principles from the Repository

The following architectural principles emerge from skills/review-animations/STANDARDS.md, skills/apple-design/SKILL.md, and skills/prototype/PICKER.md.

Never Eliminate Feedback

Users still require visual cues for state changes such as button activation or selection. As noted in skills/review-animations/STANDARDS.md at line 62, you should preserve opacity or color changes while removing only the movement-heavy transforms. Eliminating all feedback creates accessibility barriers for users who rely on visual confirmation of interactions.

Prefer Opacity and Color Over Transform

According to skills/apple-design/SKILL.md at line 200, swap slides, springs, and parallax effects for short opacity cross-fades. Color changes that aid comprehension should be retained, as these properties are less likely to cause vestibular issues compared to spatial movement.

Gracefully Disable Heavy Motion

The skills/prototype/PICKER.md file at line 70 demonstrates wrapping transform-based transitions inside @media (prefers-reduced-motion: reduce) blocks and setting them to none. This ensures the user’s system-level Reduced Motion preference is respected without requiring JavaScript detection.

Gate Hover and Gesture Animations

Touch devices fire false hover events, and reduced-motion users may have hover disabled. Combine prefers-reduced-motion with @media (hover: hover) and (pointer: fine) to conditionally enable hover effects only on devices that properly support them.

Maintain Consistent Timing

A sudden removal of motion should not alter perceived responsiveness. Keep animation durations short (≤ 200 ms) and use identical easing curves for both reduced-motion and normal paths to ensure the interface feels equally responsive regardless of user settings.

Implementation Examples

CSS-Only Reduction

As implemented in skills/prototype/PICKER.md, wrap transform overrides in a media query while preserving color transitions:

/* Normal animation */
.button {
  transition: background-color 150ms ease, transform 250ms var(--ease-out);
  transform: translateY(0);
}

/* Reduced‑motion override */
@media (prefers-reduced-motion: reduce) {
  .button {
    transition: background-color 150ms ease; /* keep color fade */
    transform: none;                         /* drop movement */
  }
}

React Hook with useReducedMotion

The Apple Design skill recommends using Framer Motion’s useReducedMotion hook to programmatically adapt animations:

import { useReducedMotion } from 'framer-motion';

function FancyCard() {
  const shouldReduce = useReducedMotion(); // respects the media query
  const animation = shouldReduce
    ? { opacity: 1, transform: 'none' }          // cross‑fade only
    : { opacity: 1, transform: 'translateY(0)' }; // normal slide‑in

  return (
    <motion.div
      initial={{ opacity: 0, transform: 'translateY(20px)' }}
      animate={animation}
      transition={{ duration: 0.2, ease: 'easeOut' }}
      className="card"
    />
  );
}

Guarding Hover Effects on Touch Devices

Following the accessibility recommendations in skills/review-animations/STANDARDS.md, combine pointer and motion queries:

/* Enable hover scaling only on devices that support hover */
@media (hover: hover) and (pointer: fine) {
  .icon:hover {
    transform: scale(1.08);
    transition: transform 120ms ease-out;
  }
}

/* Ensure reduced‑motion users never get the scale */
@media (prefers-reduced-motion: reduce) {
  .icon:hover {
    transform: none;
  }
}

Summary

  • Wrap transform transitions in @media (prefers-reduced-motion: reduce) blocks to disable movement while keeping the interface responsive.
  • Replace motion-heavy effects with opacity fades or color shifts to maintain essential visual feedback.
  • Use the useReducedMotion hook in React components to adapt animations programmatically.
  • Gate hover effects using @media (hover: hover) and (pointer: fine) to prevent false triggers on touch devices.
  • Test computed styles in browser DevTools with "Reduced motion" enabled to verify only intended properties change.

Frequently Asked Questions

What is the prefers-reduced-motion media query?

The prefers-reduced-motion media query detects whether a user has requested that the system minimize non-essential motion. It returns reduce when the user has enabled the Reduced Motion accessibility setting, or no-preference when they have not.

Should I remove all animations for users who prefer reduced motion?

No, you should never eliminate feedback entirely. Remove only transform-based movements like slides, scales, and rotations. Preserve opacity fades and color changes that communicate state changes, as these provide necessary visual cues without causing vestibular discomfort.

How do I detect reduced motion preferences in JavaScript?

Use window.matchMedia('(prefers-reduced-motion: reduce)') to check the preference in vanilla JavaScript, or use the useReducedMotion hook from Framer Motion in React applications. Both methods respect the system-level setting and update dynamically if the user changes their preferences.

How can I test reduced motion settings during development?

Open your browser’s DevTools and enable the "Reduced motion" emulation in the rendering or accessibility panel. Inspect computed styles to verify that transform properties are disabled while opacity and color transitions remain active, ensuring your implementation matches the standards defined in emilkowalski/skills.

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 →