Reduced motion

How components follow the reduced motion preference, and how to force a subtree to play or stay still.

#Default behaviour

When the user enables reduced motion in their OS (prefers-reduced-motion: reduce), components adapt automatically:

  • Entrance animations render their final state without moving or fading.
  • WebGL backgrounds draw a single still frame and stop rendering.
  • Looping CSS animations stop.

#usePrefersReducedMotion

Read the same preference in your own components to keep the whole site consistent. It also respects any surrounding ReducedMotionProvider.

HeroVideo.tsx
import { usePrefersReducedMotion } from '@elyri/motion';

export function HeroVideo() {
  const reduced = usePrefersReducedMotion();
  return reduced ? <img src="/hero.jpg" alt="" /> : <video src="/hero.mp4" autoPlay muted loop />;
}

#ReducedMotionProvider

Force a subtree to stay still (reduced) or play (reduced={false}) regardless of the system setting. Useful for thumbnail grids or an in-app “disable animations” toggle.

Thumbnail.tsx
import { Aurora, ReducedMotionProvider } from '@elyri/motion';

export function Thumbnail({ playing }: { playing: boolean }) {
  // Still until hovered, then play
  return (
    <ReducedMotionProvider reduced={!playing}>
      <Aurora />
    </ReducedMotionProvider>
  );
}
Forcing reduced={false} overrides the user’s system setting — only do it when the user explicitly opts in.

#Performance

  • WebGL backgrounds pause offscreen and in background tabs, and resume when visible.
  • Rendering is capped at 2× device pixel ratio by default to keep GPU cost in check on dense screens.
  • During server rendering the preference is treated as “no reduction” and switches to the real value after hydration, avoiding mismatches.