A CLAUDE.md is just a markdown file at the root of your repo. Copy the content below into your own project's CLAUDE.md to give your agent the same context.
npx versuz@latest install motiondivision-motion-vue --kind=claude-mdcurl -o CLAUDE.md https://raw.githubusercontent.com/motiondivision/motion-vue/HEAD/CLAUDE.md# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
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.
## Key Commands
### Development
- `pnpm dev` - Start development watch mode for motion package
- `pnpm build` - Build the motion package and plugins
- `pnpm test` - Run unit tests for motion package
- `pnpm test:e2e` - Run Playwright end-to-end tests
- `pnpm play` - Start the Nuxt playground for interactive testing
### Documentation
- `pnpm docs:dev` - Start documentation development server
- `pnpm docs:build` - Build documentation site
### Testing
- 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`
### Linting & Formatting
- 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
## Architecture
### Package Structure
The monorepo contains three main packages:
- `packages/motion/` - Core motion library (published as `motion-v`)
- `packages/plugins/` - Nuxt module and resolver for unplugin-vue-components
- `playground/nuxt/` - Nuxt playground (run with `pnpm play`)
- `playground/vite/` - Vite playground for E2E tests (runs on port 5173)
- `docs/` - Documentation site
### Core Components Architecture
Motion components are built on top of Framer Motion's core, with Vue-specific adaptations:
1. **Motion Component System** (`packages/motion/src/components/motion/`)
- Creates motion-enabled HTML/SVG elements using `createMotionComponent` factory
- Supports `asChild` prop for applying motion to child elements (template mode)
- Uses `useMotionState` composable to initialize and manage state
- Caches components for performance (separate caches for mini/max feature sets)
- Main export is `motion` object with `.create()` method for any HTML/SVG tag
- `useMotionState` lifecycle order: `onMounted` (mount), `onBeforeUpdate` (beforeUpdate + updateOptions), `onUpdated` (update), `onBeforeUnmount` (beforeUnmount), `onUnmounted` (unmount if disconnected)
- Props are updated via `state.updateOptions()` in `onBeforeUpdate` (not `onUpdated`) so parent variant context is available to children during their update cycle
- Props merging (layoutId namespacing, transition defaults, presence initial) is handled by `resolveMotionProps` utility (`src/utils/resolve-motion-props.ts`), shared with the `v-motion` directive
2. **Visual Element State** (`packages/motion/src/state/`)
- Core `MotionState` class 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 `mountedStates` WeakMap
3. **Feature System** (`packages/motion/src/features/`)
- Modular feature loading via `FeatureManager`
- Two feature bundles: `domAnimation` (minimal) and `domMax` (full)
- Each feature extends `Feature` base 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)
4. **Animation Controls** (`packages/motion/src/animation/`)
- Provides imperative animation controls via `useAnimationControls`
- Manages animation sequencing and orchestration across components
5. **Scroll Tracking** (`packages/motion/src/value/use-scroll.ts`)
- `useScroll(options?)` composable returns `{ scrollX, scrollY, scrollXProgress, scrollYProgress }` as motion values
- `container` and `target` options accept `MaybeComputedElementRef` (supports Vue component instances via `getElement`)
- `axis` and `offset` options accept `MaybeRefOrGetter` (resolved with `toValue`, supports both refs and getter functions)
- Uses `watchEffect` with `flush: 'post'` to reactively re-subscribe when reactive options change
- SSR-safe: skips scroll setup when `isSSR` is true
- Delegates to Framer Motion's `scroll` function from `framer-motion/dom`
6. **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 `LayoutGroup` component
7. **AnimatePresence** (`packages/motion/src/components/animate-presence/`)
- Manages exit animations for components being removed from the DOM
- Wraps Vue's `Transition`/`TransitionGroup` components
- Provides presence context to child motion components
- Handles popLayout feature to prevent layout shift during exit animations
- `custom` prop is synced eagerly inside the `exit()` hook (not via a Vue watcher) because Vue's `@leave` fires synchronously during patching — a `flush: 'pre'` watcher is not guaranteed to have run when `v-if` and `custom` change in the same tick
- `presenceContext` is passed to `visualElement` both at init (`initVisualElement`) and on updates (`updateOptions`) so exit variant functions receive the correct `custom` value
8. **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 `createPresetDirective` can be overridden per-use via binding value
- Registered globally via `MotionPlugin` (supports `presets` option) or Nuxt module (`motionV.directives: true`)
### Build Configuration
- 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 `afterBuild` hook
- Outputs preserve module structure with `preserveModules: true`
- Type declarations generated via `vite-plugin-dts` in `dist/es/` directory
### Testing Strategy
- 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`
## Important Implementation Notes
1. **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.
2. **Component Rendering**: Motion components use Vue's dynamic component system with render functions. The `asChild` prop enables applying motion to child elements without wrapper divs. Self-closing tags (area, img, input) are handled specially.
3. **State Management**: Visual element state is managed through `MotionState` class which bridges Vue reactivity with Framer Motion's store system. The `mountedStates` WeakMap tracks which elements have motion state.
4. **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)
5. **Gesture Handling**: Gesture features extend the `Feature` base class and attach event listeners during mount. Each gesture manages its own state and cleanup.
6. **Performance**: The library uses lazy loading via `LazyMotion` component, component caching, and careful lifecycle management to optimize performance. Features can be loaded on-demand to reduce bundle size.
## Development Workflow
1. **Building**: Always run `pnpm build` after modifying the motion package before testing in playground. The build includes both the motion package and plugins (triggered automatically).
2. **Testing Changes**:
- Use `pnpm play` for 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
3. **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 `/tests` directory using Playwright
4. **Git Workflow**:
- Commits must follow conventional commit format (enforced by commitlint)
- Pre-commit hooks run ESLint auto-fix via lint-staged
- Use `pnpm bumpp` to version bump all packages together
5. **Common Issues**:
- If playground doesn't reflect changes, ensure you ran `pnpm build`
- Plugin builds happen automatically after motion build via `afterBuild` hook
- Watch mode available with `pnpm dev` for iterative development
- If exit variant functions do not receive the `custom` value: ensure `presenceContext` is threaded through `initVisualElement` and `updateOptions` in `MotionState`, and that `custom` is synced eagerly at the start of the `exit()` hook in `usePresenceContainer`