# How to Implement prefers-reduced-motion Correctly: CSS and JavaScript Guide

> Learn to implement prefers reduced motion with CSS and JavaScript. Apply CSS media queries and a React hook to disable animations for users sensitive to motion, enhancing accessibility.

- Repository: [Emil Kowalski/skills](https://github.com/emilkowalski/skills)
- Tags: how-to-guide
- Published: 2026-07-29

---

**Wrap motion-heavy CSS animations in `@media (prefers-reduced-motion: reduce)` overrides and use a `useReducedMotion` hook to gate JavaScript-driven animations, replacing transforms with opacity or color transitions.**

The `prefers-reduced-motion` media feature is essential for accessible web interfaces that respect user accessibility settings. According to the emilkowalski/skills repository standards, properly handling this setting prevents motion sickness while preserving necessary visual feedback for state changes. This guide covers the implementation patterns found in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) and related skill files.

## CSS-Only Implementation with Media Queries

The most reliable method to honor user preferences is to wrap animation properties in a media query that only applies when the user has not requested reduced motion. When `reduce` is detected, replace heavy transforms with gentler effects like opacity or color changes.

In [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), the recommended pattern shows how to maintain visual feedback while removing problematic motion:

```css
/* Default animation (used when reduced-motion is NOT requested) */
.element {
  animation: slideIn 300ms ease-out forwards;
}

/* Reduced-motion override – keep only opacity/color changes */
@media (prefers-reduced-motion: reduce) {
  .element { animation: fade 0.2s ease; } /* keep opacity/color, drop transform-based motion */
}

```

This approach ensures that users who require reduced motion still receive state-change feedback through subtle transitions rather than position-shifting transforms.

## JavaScript Detection in React

For JavaScript-driven animations using libraries like Framer Motion or Spring, you need runtime detection. The `matchMedia` API allows you to query the preference and create a reactive hook.

Create a `useReducedMotion` hook as shown in the skills repository:

```tsx
// src/hooks/useReducedMotion.ts
import { useEffect, useState } from 'react';

export function useReducedMotion(): boolean {
  const [reduce, setReduce] = useState(() =>
    typeof window !== 'undefined'
      ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
      : false,
  );

  useEffect(() => {
    const media = window.matchMedia('(prefers-reduced-motion: reduce)');
    const handler = () => setReduce(media.matches);
    media.addEventListener('change', handler);
    return () => media.removeEventListener('change', handler);
  }, []);

  return reduce;
}

```

Use this hook to conditionally adjust animation values, as demonstrated in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md):

```tsx
import { useReducedMotion } from '@/hooks/useReducedMotion';

function Drawer() {
  const reduce = useReducedMotion();
  const closedX = reduce ? 0 : '-100%';   // Skip slide when reduced-motion is on

  return (
    <motion.div
      animate={{ x: closedX }}
      transition={{ duration: reduce ? 0.2 : 0.3, ease: 'easeOut' }}
    />
  );
}

```

## Gating Hover Effects for Input Devices

Not all devices support hover, and hover-driven motion can be particularly problematic for touch users. Combine `prefers-reduced-motion` with `hover` and `pointer` media features to limit effects to appropriate devices.

```css
@media (hover: hover) and (pointer: fine) {
  .button:hover { transform: scale(1.05); } /* only on true hover devices */
}

```

This pattern from the repository's accessibility guidelines ensures that scale transforms and other hover effects only activate on devices with fine pointer control, avoiding unexpected motion on touchscreens.

## The "Gentle, Not Zero" Design Principle

The emilkowalski/skills repository emphasizes that reduced motion does not mean removing all animations. According to [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), you should keep transitions that aid comprehension while removing movement that causes discomfort.

> “Reduced motion means fewer and gentler animations, not zero — keep transitions that aid comprehension, remove movement/position changes.”

When `prefers-reduced-motion: reduce` is active:

- **Remove**: Large position changes, parallax effects, spinning loaders
- **Keep**: Subtle opacity fades, color transitions, and scale changes that indicate state

## Testing Your Implementation

### Manual Testing

Toggle Reduced Motion in your operating system settings, or use Chrome DevTools:

1. Open DevTools → Rendering tab
2. Enable "Emulate CSS media feature prefers-reduced-motion: reduce"

### Automated Testing

In Jest or React Testing Library, mock `window.matchMedia` to return `matches: true` and assert that your components render with fallback styles or values:

```tsx
// Mock matchMedia for reduced motion tests
window.matchMedia = jest.fn().mockImplementation(query => ({
  matches: query === '(prefers-reduced-motion: reduce)',
  addEventListener: jest.fn(),
  removeEventListener: jest.fn(),
}));

```

## Summary

- **Use `@media (prefers-reduced-motion: reduce)`** in CSS to override transform-based animations with opacity or color alternatives, as specified in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md).
- **Implement a `useReducedMotion` hook** using `window.matchMedia` to gate JavaScript animations in React components.
- **Combine with hover media features** to ensure motion only occurs on appropriate input devices.
- **Follow the "gentle, not zero" principle** by maintaining subtle transitions that convey state changes without causing motion sickness.
- **Test manually** via OS settings or DevTools, and **automate** with mocked `matchMedia` in your test suite.

## Frequently Asked Questions

### What is prefers-reduced-motion and why does it matter?

The `prefers-reduced-motion` CSS media feature detects when a user has requested reduced motion in their operating system settings. It matters for accessibility because unexpected animations can trigger vestibular disorders, causing dizziness or nausea for sensitive users. According to [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md), respecting this preference is a core accessibility requirement for modern web applications.

### Should I remove all animations when prefers-reduced-motion is enabled?

No. As documented in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), reduced motion should mean "gentler, not zero." Remove problematic transforms like slides and parallax, but keep subtle opacity and color transitions that help users understand state changes. The [`skills/apple-design/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md) file specifically recommends replacing slide animations with cross-fades rather than removing feedback entirely.

### How do I detect prefers-reduced-motion in JavaScript without React?

Use the native `matchMedia` API: `const media = window.matchMedia('(prefers-reduced-motion: reduce)');` Check `media.matches` for the current state and attach a `change` event listener to detect live updates. This works in vanilla JavaScript or any framework, though the React hook pattern in [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md) provides a convenient wrapper for component-based architectures.

### Which repository files contain the official standards for reduced motion?

The primary reference is [`skills/review-animations/STANDARDS.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/STANDARDS.md), which contains the CSS media query patterns and React hook examples. Additional context appears in [`skills/review-animations/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/review-animations/SKILL.md) for accessibility expectations, [`skills/improve-animations/AUDIT.md`](https://github.com/emilkowalski/skills/blob/main/skills/improve-animations/AUDIT.md) for audit checklists, and [`skills/emil-design-eng/SKILL.md`](https://github.com/emilkowalski/skills/blob/main/skills/emil-design-eng/SKILL.md) for design implementation guidance.