{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hooks-use-prefers-reduced-motion",
  "title": "usePrefersReducedMotion",
  "description": "A hook that tracks the OS-level Reduced Motion setting live, so GSAP-driven primitives can skip or shortcut their animations.",
  "files": [
    {
      "path": "registry/hooks/use-prefers-reduced-motion/index.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useLayoutEffect, useState } from \"react\";\n\nconst REDUCED_MOTION_QUERY = \"(prefers-reduced-motion: reduce)\";\n\n// useLayoutEffect is a no-op (with a console warning) during SSR — fall back\n// to useEffect there since there's no window/matchMedia to read anyway.\nconst useIsomorphicLayoutEffect =\n  typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\n/**\n * Tracks the OS-level Reduced Motion setting, live. Unlike a one-time\n * `matchMedia(...).matches` check, this updates if the user toggles the\n * setting while the page is open (no reload needed).\n *\n * Defaults to `true`, not `false`. A consumer that mounts a GSAP\n * ScrollTrigger off this value reads it during its *first* render's effects\n * no matter which effect phase either hook uses — React doesn't re-render\n * mid-flush just because this hook's own effect called setState. Defaulting\n * to `false` meant every mount briefly created a real, pinned ScrollTrigger\n * before correcting a render later; anyone scrolling in that window saw the\n * full animation despite reduced motion being on. Defaulting to `true`\n * flips which side gets the one-frame guess: a non-reduced-motion user\n * waits one imperceptible frame for the animation to start, instead of a\n * reduced-motion user getting a live trigger they were never supposed to.\n */\nexport function usePrefersReducedMotion(): boolean {\n  const [prefersReducedMotion, setPrefersReducedMotion] = useState(true);\n\n  useIsomorphicLayoutEffect(() => {\n    const media = window.matchMedia(REDUCED_MOTION_QUERY);\n    const update = () => {\n      setPrefersReducedMotion(media.matches);\n    };\n    update();\n    media.addEventListener(\"change\", update);\n    return () => {\n      media.removeEventListener(\"change\", update);\n    };\n  }, []);\n\n  return prefersReducedMotion;\n}\n",
      "type": "registry:hook",
      "target": "hooks/use-prefers-reduced-motion.tsx"
    }
  ],
  "type": "registry:hook"
}
