logo

useReducedMotion

useReducedMotion returns a Svelte readable store that reflects the user’s prefers-reduced-motion accessibility setting. The store updates live when the media query changes, so components can disable or simplify animations the moment the user toggles the OS preference.

<script>
    import { useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()
</script>

<div style:transform={$reduced ? 'none' : 'rotate(45deg)'}>
    Respects the user's preference
</div>
<script>
    import { useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()
</script>

<div style:transform={$reduced ? 'none' : 'rotate(45deg)'}>
    Respects the user's preference
</div>

Why it matters

Some users disable motion at the OS level because animations cause vestibular discomfort, distraction, or other accessibility issues. useReducedMotion gives your components a single source of truth so they can opt out of motion gracefully rather than ignoring the user’s setting.

OS preference: no-preference

Tip: Chrome DevTools → Rendering → emulate prefers-reduced-motion: reduce to test the OS path.

Usage

The hook returns a Readable<boolean> — subscribe with $ like any Svelte store:

<script lang="ts">
    import { useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()
</script>

{#if $reduced}
    <p>Animations have been disabled to respect your preference.</p>
{:else}
    <FancyAnimatedHero />
{/if}
<script lang="ts">
    import { useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()
</script>

{#if $reduced}
    <p>Animations have been disabled to respect your preference.</p>
{:else}
    <FancyAnimatedHero />
{/if}

Skipping motion in motion components

Combine with motion to swap an animated transition for an instant change when reduced motion is requested:

<script lang="ts">
    import { motion, useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()
</script>

<motion.div
    animate={{ x: 100 }}
    transition={$reduced ? { duration: 0 } : { type: 'spring', stiffness: 200 }}
/>
<script lang="ts">
    import { motion, useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()
</script>

<motion.div
    animate={{ x: 100 }}
    transition={$reduced ? { duration: 0 } : { type: 'spring', stiffness: 200 }}
/>

With variants

When you build variants, fall back to a “no motion” variant for users who opt out:

<script lang="ts">
    import { motion, useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()

    const variants = {
        hidden: { opacity: 0, y: 20 },
        visible: { opacity: 1, y: 0 }
    }

    const reducedVariants = {
        hidden: { opacity: 0 },
        visible: { opacity: 1 }
    }
</script>

<motion.div
    variants={$reduced ? reducedVariants : variants}
    initial="hidden"
    animate="visible"
/>
<script lang="ts">
    import { motion, useReducedMotion } from '@humanspeak/svelte-motion'

    const reduced = useReducedMotion()

    const variants = {
        hidden: { opacity: 0, y: 20 },
        visible: { opacity: 1, y: 0 }
    }

    const reducedVariants = {
        hidden: { opacity: 0 },
        visible: { opacity: 1 }
    }
</script>

<motion.div
    variants={$reduced ? reducedVariants : variants}
    initial="hidden"
    animate="visible"
/>

How it works

  • Subscribes to window.matchMedia('(prefers-reduced-motion: reduce)').
  • Uses MediaQueryList change events; falls back to the legacy addListener API for Safari < 14.
  • Removes the listener automatically when the last subscriber unsubscribes.
  • Returns false in SSR or environments without matchMedia, so it is safe to call during server rendering.

API Reference

Returns

Readable<boolean>true when the user has requested reduced motion, otherwise false.

Testing the preference

You don’t have to change OS settings to verify your reduced-motion code paths:

  • Chrome / Edge DevTools: open DevTools → ⋯ → More tools → Rendering → Emulate CSS media feature prefers-reduced-motionreduce.
  • Firefox: set ui.prefersReducedMotion to 1 in about:config.
  • Playwright: test.use({ reducedMotion: 'reduce' }) or await page.emulateMedia({ reducedMotion: 'reduce' }) in a test.

See also


Based on Motion’s useReducedMotion API.