# How to Implement Reduced Motion Support for Accessibility in Taste-Skill

> Learn how to implement accessibility reduced motion support in Taste-Skill using the useReducedMotion hook and CSS media guards to enhance user experience for sensitive individuals.

- Repository: [Leon Lin/taste-skill](https://github.com/Leonxlnx/taste-skill)
- Tags: how-to-guide
- Published: 2026-06-05

---

**Taste-Skill mandates that any animation with a `MOTION_INTENSITY` above 3 must respect the user's `prefers-reduced-motion` setting by combining the `useReducedMotion()` hook from `motion/react` with CSS `@media` guards.**

The `Leonxlnx/taste-skill` repository enforces strict accessibility guardrails to ensure motion does not create barriers for users with vestibular disorders. Implementing reduced motion support for accessibility is a non-negotiable requirement when working with high-intensity animations within this design system. This guide breaks down the two-layer approach—React-level detection and CSS-level media queries—codified in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md).

## The Motion Intensity Threshold

According to [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) (lines 525–529), Taste-Skill divides motion into intensity levels. Any animation whose **`MOTION_INTENSITY`** exceeds **3** must unconditionally respect the user's reduced-motion setting. Motion levels **1–3** are considered static and do not require additional reduced-motion handling, as noted in [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md) line 561. This rule applies across the entire codebase and is not optional.

## Two-Layer Implementation Strategy

Taste-Skill requires both a React and a CSS layer because projects typically contain a mix of **Motion**-driven JavaScript animations and pure CSS transitions.

- **React / Framer Motion layer**: Use the `useReducedMotion()` hook from the `motion/react` package. This hook returns `true` when the OS-level reduced-motion preference is active, letting components conditionally render static variants or skip animation props entirely.
- **CSS layer**: Guard keyframes and transitions inside `@media (prefers-reduced-motion: no-preference)`. Provide a `@media (prefers-reduced-motion: reduce)` override that disables or replaces the animation with instantaneous state changes.

Applying both layers ensures that no animated path leaks through, regardless of whether the motion originates from a `motion.div` or a stylesheet.

## Step-by-Step Guide to Implement Reduced Motion Support for Accessibility

### 1. Detect the Preference

Import `useReducedMotion` inside any animated component. As implemented in `Leonxlnx/taste-skill`, this hook queries the user's system preference directly:

```ts
import { useReducedMotion } from "motion/react";

const prefersReduced = useReducedMotion(); // true → user prefers reduced motion

```

### 2. Conditionally Apply Animation Props

When `prefersReduced` is `true`, pass `animate={false}` or render a static variant. For motion intensity levels 1–3, simple hover effects can remain because they fall within the static threshold. High-intensity effects must be gated:

```tsx
animate={prefersReduced ? staticVariant : animatedVariant}

```

### 3. Wrap CSS Animations in Media Queries

Place default animated styles inside `@media (prefers-reduced-motion: no-preference)` and provide a reduce fallback. This pattern lives in your global stylesheet or CSS modules:

```css
@media (prefers-reduced-motion: no-preference) {
  .fade-in { animation: fadeIn 0.6s ease-out; }
}

@media (prefers-reduced-motion: reduce) {
  .fade-in { animation: none; }
}

```

### 4. Avoid Forbidden Animation Patterns

The **Performance & Accessibility Guardrails** section in [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md) (lines 511–514) explicitly bans `window.addEventListener('scroll')` and manual `scrollY` calculations. Rely on Motion hooks such as `useScroll` and `useMotionValue`, or use CSS scroll-timeline approaches. This fail-fast policy prevents jank and preserves user control over motion.

### 5. Test the Reduced-Motion Experience

Verify behavior across both states:

- Confirm the component animates when the OS preference is set to *no-preference*.
- Toggle the OS setting to *reduce*, or simulate it via Chrome DevTools, and verify that all motion collapses to static.
- Run a Lighthouse Accessibility audit; the reduced-motion check should pass.

## Code Examples for Reduced Motion Support

### React Component with Motion and Reduced-Motion Fallback

The following component conditionally swaps between a spring animation and an instantaneous static state:

```tsx
import { motion, useReducedMotion } from "motion/react";

export function Card({ title, children }: { title: string; children: ReactNode }) {
  const prefersReduced = useReducedMotion();

  // Simple static variant for reduced‑motion users
  const staticVariant = { opacity: 1, y: 0 };
  const animatedVariant = {
    opacity: 1,
    y: 0,
    transition: { type: "spring", stiffness: 120 },
  };

  return (
    <motion.article
      initial={{ opacity: 0, y: 20 }}
      animate={prefersReduced ? staticVariant : animatedVariant}
      className="p-6 bg-white dark:bg-zinc-900 rounded-lg shadow-lg"
    >
      <h2 className="text-xl font-medium">{title}</h2>
      {children}
    </motion.article>
  );
}

```

### CSS Guard for Fade-In Animations

Use layered media queries to disable keyframe animations for users who request reduced motion:

```css
/* styles.css */
@media (prefers-reduced-motion: no-preference) {
  .fade-in {
    animation: fadeIn 0.5s ease-out forwards;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fade-in {
    animation: none;
  }
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

```

### Scroll-Driven Animation with Static Fallback

This pattern uses `useScroll` and `useTransform` while respecting reduced-motion preferences:

```tsx
import { motion, useScroll, useTransform, useReducedMotion } from "motion/react";

export function ParallaxBanner() {
  const prefersReduced = useReducedMotion();
  const { scrollYProgress } = useScroll();

  // Motion value is ignored when reduced‑motion is active
  const y = useTransform(
    scrollYProgress,
    [0, 1],
    prefersReduced ? [0, 0] : [0, -200]   // static when reduced
  );

  return (
    <motion.div style={{ y }} className="h-64 bg-cover bg-center">
      {/* background image */}
    </motion.div>
  );
}

```

## Key Files and References

- **[`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md)** — Central design-system specification. Lines 525–529 define the reduced-motion mandate, and lines 511–514 list forbidden animation patterns.
- **[`CHANGELOG.md`](https://github.com/Leonxlnx/taste-skill/blob/main/CHANGELOG.md)** — Records the introduction of the reduced-motion requirement.
- **Component files importing `motion/react`** — Where `useReducedMotion()` is applied in practice (e.g., [`components/Card.tsx`](https://github.com/Leonxlnx/taste-skill/blob/main/components/Card.tsx) or equivalent).
- **Global stylesheet (e.g., [`styles/globals.css`](https://github.com/Leonxlnx/taste-skill/blob/main/styles/globals.css))** — Holds CSS `@media (prefers-reduced-motion)` guards.

## Summary

- Taste-Skill mandates reduced-motion support for any animation where `MOTION_INTENSITY` is greater than 3, per [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md) lines 525–529.
- Implement a **React layer** using `useReducedMotion()` from `motion/react` to toggle animation props at runtime.
- Implement a **CSS layer** using `@media (prefers-reduced-motion: reduce)` to disable transitions and keyframes.
- Avoid forbidden patterns such as `window.addEventListener('scroll')`; use Motion hooks like `useScroll` instead.
- Test in both OS preference states and verify through Lighthouse accessibility audits.

## Frequently Asked Questions

### What is the MOTION_INTENSITY threshold that triggers reduced-motion support in Taste-Skill?

Any animation whose `MOTION_INTENSITY` exceeds **3** must unconditionally respect the user's `prefers-reduced-motion` setting. Motion levels **1–3** are classified as static and do not require additional reduced-motion handling, as documented in [`skills/taste-skill/SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/skills/taste-skill/SKILL.md) line 561.

### Why does Taste-Skill require both React and CSS layers for reduced motion?

The codebase contains a mix of **Motion**-driven JavaScript animations and pure CSS transitions. Using only one layer risks leaking motion through the other path. The `useReducedMotion()` hook covers React components, while `@media (prefers-reduced-motion)` guards cover stylesheet-based animations.

### Can I use `window.addEventListener('scroll')` for scroll-driven animations?

No. The Taste-Skill guardrails in [`SKILL.md`](https://github.com/Leonxlnx/taste-skill/blob/main/SKILL.md) lines 511–514 explicitly forbid `window.addEventListener('scroll')` and manual `scrollY` calculations. You must rely on Motion hooks such as `useScroll` and `useMotionValue`, or use CSS scroll-timeline features.

### How do I test reduced motion support during development?

Toggle your operating system's reduced-motion setting, or simulate the preference through Chrome DevTools. Verify that animated elements render in a static state. Finally, run a Lighthouse Accessibility audit to confirm the reduced-motion check passes.