All skills
skilld-dev avatar

/motion-v-skilld

@b9c39cb

ALWAYS load when writing or debugging code that imports "motion-v" (Motion for Vue, motion-vue). Covers the motion component, AnimatePresence, variants, gestures, layout, scroll, motion values, the v-motion directive, and the Nuxt module for version 2.4.4. Provides API facts, recipes, best practices, and migration notes.

Use this Skill: https://skilld.dev/gh/skilld-dev/vue-ecosystem-skills/motion-v-skilld

This session only. Nothing lands on disk.

referencesrecipes.md

≈1.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Recipes for motion-v 2.4.4

Small, runnable patterns. Combine with the rules in SKILL.md.

Enter and exit

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

const visible = ref(true)
</script>

<template>
  <button @click="visible = !visible">Toggle</button>
  <AnimatePresence>
    <motion.div
      v-if="visible"
      :initial="{ opacity: 0, y: 8 }"
      :animate="{ opacity: 1, y: 0 }"
      :exit="{ opacity: 0, y: -8 }"
    >
      Content
    </motion.div>
  </AnimatePresence>
</template>
  • mode="wait" plays exits before enters. mode="popLayout" removes exiting nodes from flow.
  • anchorX sets the popLayout exit side.
  • :initial="false" on AnimatePresence disables enter animation on first render.
  • With KeepAlive, exit and enter work since 2.4.2. See migration notes.

Stagger with variants and custom

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

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: { delayChildren: 0.1, staggerChildren: 0.05 },
  },
}
</script>

<template>
  <motion.ul :variants="container" initial="hidden" animate="show">
    <motion.li v-for="i in 5" :key="i" />
  </motion.ul>
</template>

For per element delay, use dynamic variants. Do not compute delays in reactive state:

<script setup lang="ts">
import { motion } from 'motion-v'
const items = ['a', 'b', 'c']
const variants = {
  hidden: { opacity: 0 },
  show: (i: number) => ({
    opacity: 1,
    transition: { delay: i * 0.1 },
  }),
}
</script>

<template>
  <motion.span
    v-for="(item, i) in items"
    :key="item"
    :variants="variants"
    initial="hidden"
    animate="show"
    :custom="i"
  />
</template>

The stagger() helper is also available from the root entry, useful with delayChildren.

Gestures

<motion.button
  :whileHover="{ scale: 1.05 }"
  :whilePress="{ scale: 0.95 }"
  @hoverStart="onHover"
/>

Animation goes in while* props. Logic goes in event props such as onHoverStart.

Drag

<script setup>
import { ref } from 'vue'
import { motion } from 'motion-v'
const frame = ref(null)
</script>

<template>
  <div ref="frame" class="frame">
    <motion.div
      drag
      :dragConstraints="frame"
      :dragElastic="0.2"
      :whileDrag="{ scale: 1.1 }"
    />
  </div>
</template>

Start drag from a different element with useDragControls:

<script setup>
import { motion, useDragControls } from 'motion-v'
const controls = useDragControls()
</script>

<template>
  <div @pointerdown="e => controls.start(e)" class="handle" />
  <motion.div drag="x" :dragListener="false" :dragControls="controls" />
</template>

Scroll linked animation

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

const target = ref(null)
const { scrollYProgress } = useScroll({ target, offset: ['start end', 'end start'] })
const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [0, 1, 0])
</script>

<template>
  <section ref="target">
    <motion.div :style="{ opacity }" />
  </section>
</template>

useScroll options accept element refs (dist/es/value/use-scroll.d.ts:4-7). Use ScrollOffset.Enter style presets for common ranges.

Motion value springs and templates

<script setup>
import { motion, useMotionValue, useSpring, useMotionTemplate } from 'motion-v'

const x = useMotionValue(0)
const sx = useSpring(x, { stiffness: 300, damping: 30 })
const shadow = useMotionTemplate`drop-shadow(${sx}px 10px 10px rgba(0,0,0,0.3))`
</script>

<template>
  <motion.div :style="{ x, filter: shadow }" />
</template>

Imperative animation with useAnimate

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

const [scope, animate] = useAnimate()

async function play() {
  await animate(scope.value, { x: 100 }, { type: 'spring' })
  await animate(scope.value.querySelectorAll('li'), { opacity: 1 }, { delay: 0.1 })
}
</script>

<template>
  <ul ref="scope" @click="play">
    <li v-for="i in 3" :key="i" />
  </ul>
</template>

useAnimate honors MotionConfig skipAnimations since 2.3.0.

Layout and shared layout

<script setup>
import { ref } from 'vue'
import { motion } from 'motion-v'
const wide = ref(false)
</script>

<template>
  <motion.div
    layout
    :style="{ width: wide ? '300px' : '100px' }"
    @click="wide = !wide"
  />
</template>

Shared layout across different trees uses the same layoutId:

<motion.img v-if="selected" layoutId="avatar" src="large.jpg" />
<motion.img v-else layoutId="avatar" src="thumb.jpg" />

Put changing size and position in :style. Keep borderRadius and boxShadow inline so scale correction applies. See SKILL.md rules 3 to 5.

Reorder list

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

const items = ref(['Item 1', 'Item 2', 'Item 3'])
</script>

<template>
  <Reorder.Group v-model:values="items" axis="y">
    <Reorder.Item v-for="item in items" :key="item" :value="item">
      {{ item }}
    </Reorder.Item>
  </Reorder.Group>
</template>

Since 2.4.0, axis="xy" reorders in two dimensions.

Arc motion

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

<template>
  <motion.div
    :animate="{ x: 200, y: -100 }"
    :transition="{ path: arc({ rotate: 90 }) }"
  />
</template>

arc() ships in 2.3.0. The path transition key is typed for it (dist/es/types/state.d.ts:33-44).

v-motion directive

Global registration:

import { createApp } from 'vue'
import { MotionPlugin } from 'motion-v'

const app = createApp(App)
app.use(MotionPlugin, {
  presets: {
    'fade-in': { initial: { opacity: 0 }, animate: { opacity: 1 } },
  },
})

Usage, either style, props win over the binding value:

<div v-motion :initial="{ opacity: 0 }" :animate="{ opacity: 1 }" />
<div v-motion="{ initial: { opacity: 0, x: -30 }, animate: { opacity: 1, x: 0 } }" />
<div v-fade-in />

Local, tree shaken:

import { domAnimation } from 'motion-v'
const vMotion = createMotionDirective(domAnimation)

LazyMotion

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

<template>
  <LazyMotion :features="() => import('motion-v').then(m => m.domMax)" :strict="true">
    <m.div :animate="{ x: 100 }" />
  </LazyMotion>
</template>

Load domAnimation for variants, gestures, exit. Load domMax when you also need drag and layout.

Nuxt

export default defineNuxtConfig({
  modules: ['motion-v/nuxt'],
  motionV: {
    components: true,
    directives: true,
  },
})

Options: components, utilities, prefix, directives, presets (dist/nuxt/index.d.mts:8-16).

SSR

  • Components render initial values as inline styles on the server.
  • The directive computes styles in getSSRProps (dist/es/directive/index.mjs:153-162).
  • The package is ESM only. Server runtimes must support ESM imports.

Source: SKILL.md on GitHub

No alerts8d3 checks · Risk SAFE
  • Gen Agent Trust Hub9d

    This skill is a comprehensive documentation and reference guide for the 'motion-v' (Motion for Vue) animation library. It contains best practices, API references, and issue logs to assist in writing and debugging Vue animation code. No malicious patterns or security risks were detected.

  • Socket8d

    No alerts

  • Snyk9d

    Risk: LOW · No issues

Signed by skilld at b9c39cb. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last week.

Activeupdated last week

README badge

README badge for skilld-dev/vue-ecosystem-skills/motion-v-skilld