Skip to main content
Vuetify0 is now in beta!
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

useReducedMotion

Respect or override the user’s prefers-reduced-motion setting, with a reactive flag and a body data attribute for CSS-only consumers.

Installation

Install the reduced-motion plugin in your app’s entry point:

main.ts
import { createApp } from 'vue'
import { createReducedMotionPlugin } from '@vuetify/v0'
import App from './App.vue'

const app = createApp(App)

app.use(createReducedMotionPlugin({ mode: 'system' }))

app.mount('#app')

Usage

ts
import { useReducedMotion } from '@vuetify/v0'

const motion = useReducedMotion()

// Should motion be minimized right now?
motion.isReduced.value // true | false

// Override at runtime
motion.select('always') // force reduce
motion.select('never') // force full motion
motion.select('system') // follow the OS setting

Modes

The selectedMode decides how the OS preference is interpreted.

ModeBehavior
systemFollow prefers-reduced-motion (default)
alwaysForce isReduced to true, ignoring the OS
neverForce isReduced to false, ignoring the OS
Warning

never forces full motion on users whose OS asked for less — it also flips the body attribute to no-preference, so your own reduce styles stop matching. Reserve it for an explicit, user-driven control (e.g. a settings toggle), never an app-wide default.

Need the raw OS preference regardless of selectedMode? Call usePrefersReducedMotion directly.

Persisting the override

Pass persist: true to remember a user’s manual override across reloads. This requires the storage plugin to be installed:

main.ts
import { createStoragePlugin, createReducedMotionPlugin } from '@vuetify/v0'

app.use(createStoragePlugin())
app.use(createReducedMotionPlugin({ persist: true }))

Architecture

useReducedMotion uses the plugin pattern with a media-query core and a DOM adapter:

Reduced Motion Plugin

Use controls to zoom and pan. Click outside or press Escape to close.

Reduced Motion Plugin

Adapters

Adapters control how the reduced-motion state is applied to the DOM. The default writes a body data attribute in the browser and renders the same attribute via @unhead↗︎ on the server, so the attribute is present in the initial HTML reflecting the configured mode — correct for always and never; in the default system mode the server cannot know the OS preference and renders no-preference.

AdapterImportDescription
V0ReducedMotionAdapter@vuetify/v0Browser: sets the data-reduced-motion attribute on document.body. Server: pushes bodyAttrs via @unhead and patches the entry on changes. Default.

The head plugin (e.g. @unhead/vue) must be installed before createReducedMotionPlugin, otherwise the server render silently skips the attribute.

Custom Adapters

Implement ReducedMotionAdapter to apply the state your own way. Stash any teardown on this.dispose — the plugin registers it on app.onUnmount, so it is cleaned up on app unmount:

ts
import { createReducedMotionPlugin, IN_BROWSER, ReducedMotionAdapter } from '@vuetify/v0'
import type { ReducedMotionAdapterSetupContext } from '@vuetify/v0'
import type { App } from 'vue'
import { watch } from 'vue'

class ClassReducedMotionAdapter extends ReducedMotionAdapter {
  setup (app: App, context: ReducedMotionAdapterSetupContext) {
    if (!IN_BROWSER) return

    function sync (reduced: boolean) {
      document.documentElement.classList.toggle('reduce-motion', reduced)
    }

    sync(context.isReduced.value)
    this.dispose = watch(context.isReduced, sync)
  }
}

app.use(createReducedMotionPlugin({ adapter: new ClassReducedMotionAdapter() }))

Standalone Usage

Use createReducedMotion to create a raw context without the plugin system — useful for testing or embedding in another composable. The standalone context does not set the body data attribute:

ts
import { createReducedMotion } from '@vuetify/v0'

const motion = createReducedMotion({ mode: 'system' })

motion.isReduced.value // tracks the OS setting
motion.select('always')
motion.isReduced.value // true

Inside a component or effect scope, cleanup is automatic; outside any scope, call motion.dispose() when done.

When useReducedMotion is called without an installed plugin, it returns a no-op fallback (isReduced is false, select does nothing), so consuming code never throws.

Reactivity

PropertyReactiveNotes
selectedModeActive override mode ('system' | 'always' | 'never')
isReducedtrue when motion should be minimized, considering selectedMode
select(mode)Set the active mode
dispose()Stop the OS media-query subscription

Styling

When installed, the plugin sets data-reduced-motion="reduce" | "no-preference" on document.body (and renders it server-side via @unhead↗︎, reflecting the configured mode), so stylesheets can respond without any JavaScript:

css
[data-reduced-motion="reduce"] *,
[data-reduced-motion="reduce"] ::before,
[data-reduced-motion="reduce"] ::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
}

In the default system mode the server cannot read the OS preference, so the initial HTML carries no-preference and a reduce-preferring user gets full motion until the client adapter takes over. Pair the attribute selector with a native media query for pre-hydration coverage:

css
@media (prefers-reduced-motion: reduce) {
  /* same reductions — covers first paint before the plugin runs */
}

Examples

Mode Switching

Toggle between the three modes and watch isReduced flip. The bouncing dot stops the moment motion is reduced — either because the OS reports prefers-reduced-motion: reduce in system mode, or because always forces it.

The raw OS readout — pulled from usePrefersReducedMotion — shows what the system reports independent of the override. Surface it in a settings panel so users can compare their system preference against what the app is actually applying.

FileRole
basic.vueMode switcher, reactive isReduced chip, and a data-attribute-gated animation
Motion full

The dot stops bouncing when motion is reduced. Switch to always to force reduction, or system to follow your OS setting.

OS preference reads , independent of the active mode.

API Reference

The following API details are for the useReducedMotion composable.
Was this page helpful?

Ctrl+/