motion-vue

Motion for Vue (motion-v ) is a production-ready animation library with a hybrid engine capable of hardware-accelerated 120fps animations. This skill provides patterns and best practices for building performant, accessible Vue animations.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "motion-vue" with this command: npx skills add akornmeier/claude-config/akornmeier-claude-config-motion-vue

Motion for Vue

Motion for Vue (motion-v ) is a production-ready animation library with a hybrid engine capable of hardware-accelerated 120fps animations. This skill provides patterns and best practices for building performant, accessible Vue animations.

Installation

npm install motion-v

Nuxt Integration

// nuxt.config.ts export default defineNuxtConfig({ modules: ['motion-v/nuxt'], })

unplugin-vue-components

import Components from 'unplugin-vue-components/vite' import MotionResolver from 'motion-v/resolver'

export default defineConfig({ plugins: [ vue(), Components({ resolvers: [MotionResolver()], }), ], })

Note: Auto-import doesn't support <motion /> component—import manually.

Core Concepts

The motion Component

Every HTML/SVG element has a motion equivalent: motion.div , motion.button , motion.circle , etc.

<script setup> import { motion } from 'motion-v' </script>

<template> <motion.div :initial="{ opacity: 0, y: 20 }" :animate="{ opacity: 1, y: 0 }" :transition="{ duration: 0.5 }" /> </template>

Animation Props

Prop Purpose

initial

Starting state (or false to skip enter animation)

animate

Target state to animate to

exit

State when removed (requires AnimatePresence)

transition

Animation configuration

variants

Named animation states

whileHover

State during hover

whilePress

State during press/tap

whileDrag

State during drag

whileInView

State when in viewport

whileFocus

State when focused

layout

Enable layout animations

layoutId

Shared element transitions

Animation Patterns

Basic Animation

<motion.div :initial="{ opacity: 0, scale: 0.8 }" :animate="{ opacity: 1, scale: 1 }" :transition="{ type: 'spring', stiffness: 300, damping: 20 }" />

Gesture Animations

<motion.button :whileHover="{ scale: 1.05, backgroundColor: '#3b82f6' }" :whilePress="{ scale: 0.95 }" :transition="{ type: 'spring', stiffness: 400, damping: 17 }" @hoverStart="() => console.log('hover started')" @hoverEnd="() => console.log('hover ended')" @pressStart="(e) => console.log('press started', e)" @press="(e) => console.log('press complete', e)" />

Drag Gestures

<script setup> import { motion, useDomRef } from 'motion-v'

const constraintsRef = useDomRef() </script>

<template> <motion.div ref="constraintsRef" class="container"> <motion.div drag :dragConstraints="constraintsRef" :dragElastic="0.2" :dragMomentum="true" :whileDrag="{ scale: 1.1, cursor: 'grabbing' }" /> </motion.div> </template>

Variants (Orchestration)

<script setup> const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { when: 'beforeChildren', staggerChildren: 0.1 } } }

const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0 } } </script>

<template> <motion.ul :variants="containerVariants" initial="hidden" animate="visible"

&#x3C;motion.li
  v-for="item in items"
  :key="item.id"
  :variants="itemVariants"
>
  {{ item.name }}
&#x3C;/motion.li>

</motion.ul> </template>

Exit Animations (AnimatePresence)

<script setup> import { motion, AnimatePresence } from 'motion-v' import { ref } from 'vue'

const isVisible = ref(true) </script>

<template> <AnimatePresence> <motion.div v-if="isVisible" key="modal" :initial="{ opacity: 0, scale: 0.9 }" :animate="{ opacity: 1, scale: 1 }" :exit="{ opacity: 0, scale: 0.9 }" /> </AnimatePresence> </template>

Critical: Direct children of AnimatePresence must have unique key props.

AnimatePresence Modes

<!-- sync (default): Enter/exit simultaneously --> <AnimatePresence mode="sync">

<!-- wait: New child waits for exiting child --> <AnimatePresence mode="wait">

<!-- popLayout: Exiting children pop out of layout flow --> <AnimatePresence mode="popLayout">

Dynamic Exit with custom Prop

<script setup> const variants = { enter: (direction) => ({ x: direction > 0 ? 300 : -300, opacity: 0 }), center: { x: 0, opacity: 1 }, exit: (direction) => ({ x: direction < 0 ? 300 : -300, opacity: 0 }) } </script>

<template> <AnimatePresence :custom="direction"> <motion.div :key="currentPage" :custom="direction" :variants="variants" initial="enter" animate="center" exit="exit" /> </AnimatePresence> </template>

Layout Animations

<!-- Animate layout changes --> <motion.div layout :style="{ width: isExpanded ? '200px' : '100px' }" />

<!-- Shared element transitions --> <motion.div v-if="isSelected" layoutId="highlight" />

Important: CSS changes should happen via :style , not :animate —layout handles the animation.

LayoutGroup for Coordination

<script setup> import { motion, LayoutGroup, AnimatePresence } from 'motion-v' </script>

<template> <LayoutGroup> <motion.ul layout> <AnimatePresence> <motion.li v-for="item in items" :key="item.id" layout :exit="{ opacity: 0, scale: 0.8 }" /> </AnimatePresence> </motion.ul> </LayoutGroup> </template>

Scroll Animations

Scroll-Triggered (whileInView)

<motion.div :initial="{ opacity: 0, y: 50 }" :whileInView="{ opacity: 1, y: 0 }" :inViewOptions="{ once: true, margin: '-100px' }" />

Scroll-Linked (useScroll)

<script setup> import { motion, useScroll, useSpring, useTransform } from 'motion-v'

const { scrollYProgress } = useScroll() const scaleX = useSpring(scrollYProgress, { stiffness: 100, damping: 30, restDelta: 0.001 }) </script>

<template> <motion.div class="progress-bar" :style="{ scaleX }" /> </template>

Element Progress Tracking

<script setup> import { ref } from 'vue' import { motion, useScroll, useTransform } from 'motion-v'

const targetRef = ref(null) const { scrollYProgress } = useScroll({ target: targetRef, offset: ['start end', 'end start'] // When element enters/leaves viewport })

const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0]) const y = useTransform(scrollYProgress, [0, 1], [100, -100]) </script>

<template> <motion.div ref="targetRef" :style="{ opacity, y }" /> </template>

Motion Values & Hooks

See HOOKS_REFERENCE.md for complete API documentation.

Quick Reference

Hook Purpose

useMotionValue(initial)

Reactive animated value (no re-renders)

useSpring(source, config)

Spring-based motion value

useTransform(value, input, output)

Map values to new values

useVelocity(value)

Track velocity of motion value

useScroll(options)

Track scroll position/progress

useAnimate()

Imperative animation control

useInView(ref, options)

Viewport intersection detection

useReducedMotion()

User motion preference

useAnimationFrame(callback)

Per-frame callbacks

useTime()

Elapsed time as motion value

useDragControls()

Programmatic drag control

useMotionTemplate

Template string with motion values

useDomRef()

DOM ref for constraints

Transition Options

<!-- Tween (default) --> <motion.div :animate="{ x: 100 }" :transition="{ duration: 0.5, ease: 'easeInOut' }" />

<!-- Spring (physics-based) --> <motion.div :animate="{ scale: 1.2 }" :transition="{ type: 'spring', stiffness: 300, damping: 20 }" />

<!-- Spring (duration-based) --> <motion.div :animate="{ rotate: 180 }" :transition="{ type: 'spring', duration: 0.8, bounce: 0.25 }" />

<!-- Per-value transitions --> <motion.div :animate="{ x: 100, opacity: 1 }" :transition="{ default: { type: 'spring' }, opacity: { ease: 'linear', duration: 0.2 } }" />

<!-- Keyframes with timing --> <motion.div :animate="{ x: [0, 100, 50, 100], transition: { duration: 2, times: [0, 0.3, 0.6, 1] } }" />

<!-- Repeat/loop --> <motion.div :animate="{ rotate: 360 }" :transition="{ repeat: Infinity, repeatType: 'loop', duration: 2 }" />

Spring options: stiffness , damping , mass , bounce , duration , restDelta , restSpeed

Tween options: duration , ease , delay

Repeat options: repeat (count or Infinity), repeatType ('loop'|'reverse'|'mirror'), repeatDelay

Global Configuration

MotionConfig

<script setup> import { motion, MotionConfig } from 'motion-v' </script>

<template> <MotionConfig :transition="{ duration: 0.3, ease: 'easeOut' }" reducedMotion="user"

&#x3C;App />

</MotionConfig> </template>

reducedMotion options:

  • "user" (default): Respect device settings

  • "always" : Force reduced motion

  • "never" : Ignore preference

Custom Components

Wrap any Vue component with motion capabilities:

<script setup> import { motion } from 'motion-v' import MyButton from './MyButton.vue'

// IMPORTANT: Define outside template to prevent recreation each render const MotionButton = motion.create(() => MyButton) </script>

<template> <MotionButton :whileHover="{ scale: 1.05 }" :whilePress="{ scale: 0.95 }" /> </template>

Performance Best Practices

Use motion values instead of Vue state for frequently-updated styles:

<script setup> const x = useMotionValue(0) // No re-renders on change </script>

Add willChange for transform-heavy animations:

<motion.div :style="{ willChange: 'transform' }" />

Use layout sparingly—it triggers measurements

Prefer independent transforms (x , y , scale , rotate ) over transform string

Use initial={false} to skip enter animations when not needed

Accessibility

Always respect user preferences:

<script setup> import { useReducedMotion } from 'motion-v'

const prefersReducedMotion = useReducedMotion() </script>

<template> <motion.div :animate="prefersReducedMotion ? {} : { x: 100 }" :transition="prefersReducedMotion ? { duration: 0 } : { duration: 0.5 }" /> </template>

Common Patterns

See PATTERNS.md for complete examples including:

  • Modal dialogs with backdrop

  • Accordion/collapsible content

  • Tab indicators with shared layout

  • Staggered list reveals

  • Page transitions

  • Draggable reorder lists

  • Scroll progress indicators

  • Parallax effects

Troubleshooting

Issue Solution

Exit animations not working Ensure direct child of AnimatePresence has key prop

Layout animation jittery Add layoutScroll to scrollable ancestors

Animation not smooth Check for re-renders; use motion values

Gesture not working on SVG filter Add gesture props to parent, use variants

Touch hover issues Motion handles this automatically; don't use CSS :hover

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

postgresql-psql

No summary provided by upstream source.

Repository SourceNeeds Review
General

docker

No summary provided by upstream source.

Repository SourceNeeds Review
General

chrome-debug

No summary provided by upstream source.

Repository SourceNeeds Review
General

shadcn-ui

No summary provided by upstream source.

Repository SourceNeeds Review