This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Motion for Vue is a Vue.js port of Framer Motion, providing declarative animations with a hybrid engine combining JavaScript animations and native browser APIs. The library exports components like motion, AnimatePresence, LayoutGroup, MotionConfig, and Reorder for creating animations, gestures, and layout transitions.
pnpm dev- Start development watch mode for motion packagepnpm build- Build the motion package and pluginspnpm test- Run unit tests for motion packagepnpm test:e2e- Run Playwright end-to-end testspnpm play- Start the Nuxt playground for interactive testing
pnpm docs:dev- Start documentation development serverpnpm docs:build- Build documentation site
- Single test:
pnpm --filter motion-v test [test-file-name] - Coverage:
pnpm --filter motion-v coverage - E2E tests:
pnpm test:e2e - E2E UI mode:
pnpm test:e2e:ui - E2E debug:
pnpm test:e2e:debug - E2E report:
pnpm test:e2e:report
- ESLint is configured to run automatically on pre-commit via git hooks
- Manual lint: Files are automatically fixed on commit
- Git hooks: Commitlint enforces conventional commit format
The monorepo contains three main packages:
packages/motion/- Core motion library (published asmotion-v)packages/plugins/- Nuxt module and resolver for unplugin-vue-componentsplayground/nuxt/- Nuxt playground (run withpnpm play)playground/vite/- Vite playground for E2E tests (runs on port 5173)docs/- Documentation site
Motion components are built on top of Framer Motion's core, with Vue-specific adaptations:
-
Motion Component System (
packages/motion/src/components/motion/)- Creates motion-enabled HTML/SVG elements using
createMotionComponentfactory - Supports
asChildprop for applying motion to child elements (template mode) - Uses
useMotionStatecomposable to initialize and manage state - Caches components for performance (separate caches for mini/max feature sets)
- Main export is
motionobject with.create()method for any HTML/SVG tag useMotionStatelifecycle order:onMounted(mount),onBeforeUpdate(beforeUpdate + updateOptions),onUpdated(update),onBeforeUnmount(beforeUnmount),onUnmounted(unmount if disconnected)- Props are updated via
state.updateOptions()inonBeforeUpdate(notonUpdated) so parent variant context is available to children during their update cycle - Props merging (layoutId namespacing, transition defaults, presence initial) is handled by
resolveMotionPropsutility (src/utils/resolve-motion-props.ts), shared with thev-motiondirective
- Creates motion-enabled HTML/SVG elements using
-
Visual Element State (
packages/motion/src/state/)- Core
MotionStateclass manages animation state and lifecycle - Tracks parent-child relationships for proper lifecycle ordering
- Creates visual elements through Framer Motion's HTML/SVG visual element system
- Manages active animation states (initial, animate, exit, etc.)
- Integrates with Framer Motion's store system via
mountedStatesWeakMap
- Core
-
Feature System (
packages/motion/src/features/)- Modular feature loading via
FeatureManager - Two feature bundles:
domAnimation(minimal) anddomMax(full) - Each feature extends
Featurebase class with lifecycle hooks (beforeMount, mount, update, unmount) - Gesture features:
DragGesture,HoverGesture,PressGesture,PanGesture,FocusGesture,InViewGesture - Layout features:
ProjectionFeature(FLIP animations),LayoutFeature(layout transitions) - Animation feature:
AnimationFeature(variant-based animations)
- Modular feature loading via
-
Animation Controls (
packages/motion/src/animation/)- Provides imperative animation controls via
useAnimationControls - Manages animation sequencing and orchestration across components
- Provides imperative animation controls via
-
Scroll Tracking (
packages/motion/src/value/use-scroll.ts)useScroll(options?)composable returns{ scrollX, scrollY, scrollXProgress, scrollYProgress }as motion valuescontainerandtargetoptions acceptMaybeComputedElementRef(supports Vue component instances viagetElement)axisandoffsetoptions acceptMaybeRefOrGetter(resolved withtoValue, supports both refs and getter functions)- Uses
watchEffectwithflush: 'post'to reactively re-subscribe when reactive options change - SSR-safe: skips scroll setup when
isSSRis true - Delegates to Framer Motion's
scrollfunction fromframer-motion/dom
-
Layout Animations (
packages/motion/src/features/layout/)- Handles shared layout animations between components
- Manages projection nodes for FLIP animations
- Supports layout groups for coordinated animations via
LayoutGroupcomponent
-
AnimatePresence (
packages/motion/src/components/animate-presence/)- Manages exit animations for components being removed from the DOM
- Wraps Vue's
Transition/TransitionGroupcomponents - Provides presence context to child motion components
- Handles popLayout feature to prevent layout shift during exit animations
customprop is synced eagerly inside theexit()hook (not via a Vue watcher) because Vue's@leavefires synchronously during patching — aflush: 'pre'watcher is not guaranteed to have run whenv-ifandcustomchange in the same tickpresenceContextis passed tovisualElementboth at init (initVisualElement) and on updates (updateOptions) so exit variant functions receive the correctcustomvalue
-
v-motion Directive (
packages/motion/src/)- Full-featured directive alternative to the
<motion>component — no wrapper element required - Exports:
vMotion(domMax bundle),createMotionDirective(bundle?),createPresetDirective(defaults),MotionPlugin - Supports all animation, gesture, layout, and exit props identical to
<motion> - Key limitation: does not support parent-child variant propagation (no Vue provide/inject context)
- Two syntax styles: props syntax (
v-motion :animate="...") and binding value syntax (v-motion="{ animate: ... }") — props win on conflict - Preset directives created via
createPresetDirectivecan be overridden per-use via binding value - Registered globally via
MotionPlugin(supportspresetsoption) or Nuxt module (motionV.directives: true)
- Full-featured directive alternative to the
- Uses Vite for building with separate ES (
.mjs) and CJS (.js) outputs - Includes extensive path aliasing for Framer Motion internal modules (see
vite.config.ts) - Post-build step automatically triggers plugin builds via
afterBuildhook - Outputs preserve module structure with
preserveModules: true - Type declarations generated via
vite-plugin-dtsindist/es/directory
- Unit tests use Vitest with Vue Test Utils in JSDOM environment
- E2E tests use Playwright targeting Chromium and WebKit browsers
- Test files are co-located with source code in
__tests__directories - E2E tests run against the Vite playground on port 5173
- Coverage reports available via
pnpm --filter motion-v coverage
-
Framer Motion Integration: The library wraps Framer Motion's core functionality (v12.23.26), requiring careful path aliasing to specific internal modules in
vite.config.ts. Changes to Framer Motion internals may require updating these aliases. -
Component Rendering: Motion components use Vue's dynamic component system with render functions. The
asChildprop enables applying motion to child elements without wrapper divs. Self-closing tags (area, img, input) are handled specially. -
State Management: Visual element state is managed through
MotionStateclass which bridges Vue reactivity with Framer Motion's store system. ThemountedStatesWeakMap tracks which elements have motion state. -
Context System: Uses Vue's provide/inject for passing context down the component tree:
- Motion context (parent state for variant inheritance)
- Layout group context (for shared layout animations)
- Motion config context (global configuration)
- Animate presence context (for exit animations)
- Lazy motion context (for feature tree-shaking)
-
Gesture Handling: Gesture features extend the
Featurebase class and attach event listeners during mount. Each gesture manages its own state and cleanup. -
Performance: The library uses lazy loading via
LazyMotioncomponent, component caching, and careful lifecycle management to optimize performance. Features can be loaded on-demand to reduce bundle size.
-
Building: Always run
pnpm buildafter modifying the motion package before testing in playground. The build includes both the motion package and plugins (triggered automatically). -
Testing Changes:
- Use
pnpm playfor the Nuxt playground (port 3001) - Or directly run playground with
cd playground/vite && pnpm dev(port 5173) - Changes to motion package require rebuild; playground changes hot-reload
- Use
-
Writing Tests:
- Add unit tests in
__tests__directories co-located with source - Run tests with
pnpm --filter motion-v test - E2E tests go in root
/testsdirectory using Playwright
- Add unit tests in
-
Git Workflow:
- Commits must follow conventional commit format (enforced by commitlint)
- Pre-commit hooks run ESLint auto-fix via lint-staged
- Use
pnpm bumppto version bump all packages together
-
Common Issues:
- If playground doesn't reflect changes, ensure you ran
pnpm build - Plugin builds happen automatically after motion build via
afterBuildhook - Watch mode available with
pnpm devfor iterative development - If exit variant functions do not receive the
customvalue: ensurepresenceContextis threaded throughinitVisualElementandupdateOptionsinMotionState, and thatcustomis synced eagerly at the start of theexit()hook inusePresenceContainer
- If playground doesn't reflect changes, ensure you ran