Skip to main content
The usePrefersReducedMotion hook reads the operating system’s prefers-reduced-motion setting so you can tone down or remove motion for users who need it.

Usage

import { usePrefersReducedMotion } from "@servicetitan/anvil2";

function AnimatedComponent() {
  const { prefersReducedMotion } = usePrefersReducedMotion();

  return (
    <div
      style={{
        transition: prefersReducedMotion ? "none" : "all 0.3s ease",
      }}
    >
      Content with accessible animations
    </div>
  );
}

Return Value

PropertyTypeDescription
prefersReducedMotionbooleantrue when the user prefers reduced motion ((prefers-reduced-motion: reduce) matches).

Examples

Disable transitions

import { usePrefersReducedMotion } from "@servicetitan/anvil2";

function AnimatedComponent() {
  const { prefersReducedMotion } = usePrefersReducedMotion();

  return (
    <div
      style={{
        transition: prefersReducedMotion ? "none" : "all 0.3s ease",
      }}
    >
      Content with accessible animations
    </div>
  );
}

Hover motion with CSS

Reduce transform motion when the user prefers reduced motion:
import { usePrefersReducedMotion } from "@servicetitan/anvil2";
import { type ReactNode, useState } from "react";

function HoverCard({ children }: { children: ReactNode }) {
  const { prefersReducedMotion } = usePrefersReducedMotion();
  const [hovered, setHovered] = useState(false);
  const scale = hovered && !prefersReducedMotion ? 1.05 : 1;

  return (
    <div
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      style={{
        transform: `scale(${scale})`,
        transition: prefersReducedMotion ? "none" : "transform 0.2s ease",
      }}
    >
      {children}
    </div>
  );
}

Static alternative to motion-heavy UI

When one part of the UI relies on continuous motion (marquee, auto-rotating carousel, and so on), render a static equivalent when prefersReducedMotion is true—for example a plain list or paginated controls. For libraries such as Framer Motion, install them in your application and thread prefersReducedMotion into their animation props the same way you would for CSS transition.

Technical details

Detection

The implementation uses:
window.matchMedia("(prefers-reduced-motion: reduce)");
and listens for the change event on that MediaQueryList so the returned boolean updates when the user changes the system setting.

SSR

When window is not available (including during SSR), the hook keeps prefersReducedMotion as false until the effect runs in the browser, then it updates to the real preference.

Accessibility guidelines

  1. Respect the preference: do not override reduced motion with forced animations.
  2. Preserve meaning: if motion carried information, expose that information without motion.
  3. Test with the OS setting enabled and confirm layouts and focus order stay usable.
  4. Reduced motion still allows short, subtle transitions; avoid large parallax or long loops.

How to test

  • macOS: System Settings → Accessibility → Display → Reduce motion
  • Windows: Settings → Accessibility → Visual effects → Animation effects
  • iOS: Settings → Accessibility → Motion → Reduce Motion
  • Android: Settings → Accessibility → Remove animations

Browser support

prefers-reduced-motion is supported in current browsers. Where it is unsupported, the media query does not match and the hook reports prefersReducedMotion: false.
Last modified on April 14, 2026