Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 1x 17x 12x 12x 9x 3x 6x 12x 9x 2x 7x 7x 9x 7x 7x 6x 6x 1x 1x 12x | import { useEffect, useState } from "react";
/**
* Options for the useReducedMotion hook.
*/
export interface UseReducedMotionOptions {
/**
* Value returned on the server / when `matchMedia` is unavailable.
* @default false
*/
defaultValue?: boolean;
/**
* When `true` (default), the real `matchMedia` value is read synchronously on
* the first client render. Set to `false` to render `defaultValue` on the
* first client render too and defer the real read to a post-commit effect —
* this avoids a React hydration mismatch when the server rendered
* `defaultValue` but the user actually prefers reduced motion.
* @default true
*/
initializeWithValue?: boolean;
}
const REDUCE_QUERY = "(prefers-reduced-motion: reduce)";
function isSupported(): boolean {
return (
typeof window !== "undefined" && typeof window.matchMedia === "function"
);
}
/**
* Tracks the user's `prefers-reduced-motion` accessibility setting, returning
* `true` when the user has requested reduced motion. Updates live when the
* system setting changes.
*
* Use it to disable or tone down animations for users who are sensitive to
* motion — a baseline accessibility requirement.
*
* @param options - Configuration (`defaultValue`, `initializeWithValue`)
* @returns `true` if the user prefers reduced motion
*
* @example
* ```tsx
* const reduced = useReducedMotion();
* <div style={{ transition: reduced ? "none" : "transform 300ms" }} />;
* ```
*/
export function useReducedMotion(
options: UseReducedMotionOptions = {}
): boolean {
const { defaultValue = false, initializeWithValue = true } = options;
const [reduced, setReduced] = useState<boolean>(() => {
if (!initializeWithValue || !isSupported()) {
return defaultValue;
}
return window.matchMedia(REDUCE_QUERY).matches === true;
});
useEffect(() => {
if (!isSupported()) {
return;
}
const mediaQueryList = window.matchMedia(REDUCE_QUERY);
const handleChange = () => {
setReduced(mediaQueryList.matches === true);
};
handleChange();
if (typeof mediaQueryList.addEventListener === "function") {
mediaQueryList.addEventListener("change", handleChange);
return () => mediaQueryList.removeEventListener("change", handleChange);
}
mediaQueryList.addListener(handleChange);
return () => mediaQueryList.removeListener(handleChange);
}, []);
return reduced;
}
|