JoshWComeau

usePrefersReducedMotion

Filed under
Snippets
on
in
October 6th, 2021.
Oct 2021.
const QUERY = '(prefers-reduced-motion: no-preference)';

const isRenderingOnServer = typeof window === 'undefined';

const getInitialState = () => {
  // For our initial server render, we won't know if the user
  // prefers reduced motion, but it doesn't matter. This value
  // will be overwritten on the client, before any animations
  // occur.
  return isRenderingOnServer ? true : !window.matchMedia(QUERY).matches;
};

function usePrefersReducedMotion() {
  const [prefersReducedMotion, setPrefersReducedMotion] = React.useState(
    getInitialState
  );

  React.useEffect(() => {
    const mediaQueryList = window.matchMedia(QUERY);

    const listener = (event) => {
      setPrefersReducedMotion(!event.matches);
    };

    if (mediaQueryList.addEventListener) {
      mediaQueryList.addEventListener('change', listener);
    } else {
      mediaQueryList.addListener(listener);
    }
    return () => {
      if (mediaQueryList.removeEventListener) {
        mediaQueryList.removeEventListener('change', listener);
      } else {
        mediaQueryList.removeListener(listener);
      }    };
  }, []);

  return prefersReducedMotion;
}

Link to this headingContext

For some people, motion can be harmful.

The prefers-reduced-motion CSS media query allows us to disable animations for these folks. For our animations that are entirely in CSS (eg. CSS transitions, CSS keyframe animations), this works great 💯

What about for our animations in JavaScript, though? For example, when we use a library like React Spring or Framer Motion? We need to manage it ourselves, and it becomes a pretty tricky endeavour.

For these cases, I created a use-prefers-reduced-motion hook.

I wrote a blog post all about this:

Link to this headingListening for changes

For the best possible user experience, we want to re-render components that rely on this hook when the user toggles prefers-reduced-motion on or off.

In older browsers, this is done with mediaQueryList.addListener(...). This syntax has been updated in newer browsers to be a bit more conventional: mediaQueryList.addEventListener('change', ...).

To make sure we support as many browsers as possible, we'll use both.

Link to this headingUsage

Link to this headingWith React Spring

import { useSpring, animated } from 'react-spring';

const Box = ({ isBig }) => {
  const prefersReducedMotion = usePrefersReducedMotion();

  const styles = useSpring({
    width: 100,
    height: 100,
    background: 'rebeccapurple',
    transform: isBig ? 'scale(2)' : 'scale(1)',
    immediate: prefersReducedMotion,
  });

  return <animated.div style={styles}>Box!</animated.div>;
};

Last updated on

October 6th, 2021

# of hits