@vueuse/motion
Vue composables and a v-motion directive for declarative, physics-based component animation.
Repository Health
Technical Analysis
@vueuse/motion brings Framer Motion-style declarative animation to the Vue Composition API. Instead of hand-writing CSS transitions or wiring up a general-purpose animation engine, developers attach a v-motion directive (or call useMotion directly) with initial, enter, visible, hover, and other named variants, and the library drives the underlying style and transform properties through a spring- or tween-based engine built on Popmotion.
The package ships 20+ ready-made presets (fade, pop, slide, roll — each with visible/visible-once variants driven by IntersectionObserver), a <Motion> / <MotionGroup> component pair for JSX-style usage, and first-class SSR support including a getSSRProps hook so initial animation state renders correctly on the server. A dedicated Nuxt 3 module auto-registers the plugin and composables. Everything is written in TypeScript with a small, tree-shakeable footprint (under 25kb) and no dependency on a virtual DOM diffing scheme beyond Vue’s own reactivity.
What You Get
v-motiondirective with per-elementinitial,enter,visible,visible-once,hovered,tapped, and custom variant states- 20+ built-in presets (fade, pop, slide, roll, each with directional and scroll-triggered variants) registered as
v-motion-*directives <Motion>and<MotionGroup>components for JSX/render-function usage as an alternative to the directive- Low-level composables (
useMotion,useMotionProperties,useMotionControls,useSpring,reactiveStyle,reactiveTransform) for building fully custom animation logic - SSR-safe initial state rendering via directive
getSSRProps, so server-rendered markup matches the pre-animation state - A dedicated Nuxt 3 module that auto-registers the plugin, directives, and composables with zero manual setup
useReducedMotioncomposable to respect the user’s OS-level reduced-motion preference
Common Use Cases
- Animating route/page transitions and mounted-component entrances in a Vue or Nuxt app
- Scroll-triggered reveal animations (fade/slide/roll-in) for marketing and content pages using the
visible/visible-oncevariants - Building custom hover/tap micro-interactions on buttons, cards, and list items without hand-rolled CSS transitions
- Orchestrating staggered list/group animations with
<MotionGroup>when items are added or removed - Driving physics-based (spring) animations for drag-and-drop or gesture-following UI via
useSpringanduseMotionValuesdirectly
Under The Hood
Architecture
The library is organized around a layered composable architecture: useMotion (src/useMotion.ts) composes four smaller composables — useMotionProperties (reactive style/transform state), useMotionVariants (variant/state resolution), useMotionControls (imperative apply/stop/set/leave API), and useMotionFeatures (binds event listeners, lifecycle hooks, variant syncing, and visibility hooks based on options) — each living in its own file under src/. The v-motion directive (src/directive/index.ts) is a thin adapter over useMotion: on created it resolves variants from binding value or node props, instantiates useMotion with visibility hooks deferred, and stashes the resulting MotionInstance on the DOM element; mounted then registers IntersectionObserver-based visibility hooks so SSR markup isn’t disturbed before hydration. MotionPlugin (src/plugin/index.ts) is the composition root: it registers the base directive, auto-registers a v-motion-{preset} directive per built-in preset, registers user-supplied custom directives, and registers the <Motion>/<MotionGroup> components. A flat motionState map keyed by directive binding value backs useMotions, letting multiple directive-driven instances be referenced imperatively elsewhere in an app. useMotionProperties is the shared core that every entry point ultimately depends on.
Tech Stack
Runtime dependencies are @vueuse/core/@vueuse/shared (^13) for base reactivity helpers, popmotion (^11) as the physics/tween animation engine, framesync (^6) for rAF-batched value writes, and style-value-types (^5) for CSS value parsing; vue itself is a peer dependency (>=3.0.0), keeping the package framework-version-agnostic within Vue 3. The Nuxt integration (src/nuxt) is a standard Nuxt 3 module built with @nuxt/kit/@nuxt/module-builder. Build tooling is unbuild (dual ESM output with .d.mts types) plus nuxt-module-build for the Nuxt submodule; linting runs through @antfu/eslint-config via flat config, and the monorepo uses pnpm workspaces with playgrounds/ (vite, nuxt, vite-ssg) as manually-run integration testbeds. CI runs on GitHub Actions; releases are cut with bumpp and changelogs generated via gh-changelogen, including a nightly release channel (vueuse-motion-nightly).
Code Quality
Test files cover the core public surface — useMotion, useMotionTransitions, useElementStyle, useElementTransform, reactiveStyle, reactiveTransform, isMotionInstance, and the <Motion>/<MotionGroup> components — run with Vitest plus @vue/test-utils and happy-dom, including a stored snapshot for transform-string output. Coverage is tracked via @vitest/coverage-v8 but no numeric threshold is enforced in CI. The codebase is fully TypeScript with exported types under src/types/, consistent useX composable naming matching VueUse conventions, and no obvious swallowed-error patterns in the reviewed files — options default to sensible empty values rather than throwing. Linting is enforced via @antfu/eslint-config with lint-staged/yorkie pre-commit hooks.
API Design
The core ergonomic win is that useMotion/v-motion mirror Framer Motion’s variants model almost directly, lowering the learning curve for anyone coming from the React ecosystem — variants are plain reactive objects (initial, enter, visible, hovered, custom names) and transitions are inferred rather than manually wired. Getting started requires only app.use(MotionPlugin) plus a v-motion attribute with :initial/:enter bindings, and the 20+ presets let a consumer add scroll animations with a single directive name and no JS at all. The escape hatch to lower-level composables (useMotionProperties, useSpring, reactiveStyle) is layered on the same primitives rather than a separate parallel API, and TypeScript generics (MotionVariants<T>) give autocomplete on variant names. A dedicated docs site is built from the docs/ directory with worked examples per feature, beyond what the README alone covers.
Used by 4 apps in this directory
Hook0
Devops
Open-source Webhooks-as-a-Service: deliver events to your users with auto-retry, signed payloads, and a real-time subscriber dashboard — all without building the infrastructure yourself.
NocoDB
No Code Platforms · Databases · Low Code Platforms
Turn any SQL database into a collaborative no-code spreadsheet with automatic REST APIs and real-time views.
OpnForm
Forms Surveys · Marketing
Build unlimited, embeddable forms with no code — powered by AI, logic rules, and real-time analytics
Sink
Bookmarks Archiving · Analytics
Serverless link shortener with real-time analytics, powered by Cloudflare