motion-advanced
affaan-m/everything-claude-code
Advanced motion patterns: drag, gestures, text animations, SVG drawing, and imperative sequences for React/Next.js.
What is motion-advanced?
Build complex, physics-based animations including drag-to-dismiss, swipe gestures, text reveals, SVG morphing, and multi-step sequences. Requires motion-foundations and extends motion-patterns for interactive, gesture-driven UI.
- Drag interactions: draggable cards, drag-to-dismiss sheets, reorderable lists with physics-based release
- Gesture detection: swipe left/right, long press, pinch with explicit offset and velocity thresholds
- Text animations: word-by-word reveal, character typewriter, live number counters
- SVG animations: path draw-on, icon morphing, circular progress rings with stroke interpolation
- Custom hooks: useScrollReveal, useHoverScale, useNavigationDirection, useInViewOnce for reusable patterns
- Imperative sequences via useAnimate with interrupt-safe async/await for multi-step animations
How to install motion-advanced
npx skills add https://github.com/affaan-m/everything-claude-code --skill motion-advanced- motion-foundations skill must be installed first
- React 16.8+ (hooks support)
- Next.js or React project with motion/react library
- Access to motion-tokens from motion-foundations for token values
How to use motion-advanced
- 1.Install motion-foundations skill first if not already present
- 2.Import motion components and hooks from 'motion/react'
- 3.Import springs and motionTokens from '@/lib/motion-tokens' (provided by motion-foundations)
- 4.Choose the appropriate API from the decision table based on your interaction type
- 5.Attach scope ref to a mounted DOM element when using useAnimate
- 6.Test drag and swipe interactions on actual touch devices, not just mouse
- 7.Ensure infinite animations pause when document.visibilityState is 'hidden' to save resources
Use cases
- Building a bottom sheet that dismisses when dragged past a threshold or swiped with sufficient velocity
- Creating a reorderable todo list or kanban board with smooth drag-to-reorder interactions
- Animating hero text that reveals word-by-word as the page loads or on user interaction
- Drawing an SVG icon or progress ring that animates on demand or during loading states
- Implementing a custom hook for elements that scale on hover or follow cursor movement
- React/Next.js frontend developers building interactive, gesture-driven interfaces
- Product engineers implementing advanced animations beyond basic transitions
- Mobile-first developers needing swipe and drag interactions that work on touch devices
- Designers and developers collaborating on micro-interactions and loading states
motion-advanced FAQ
Yes. motion-advanced requires motion-foundations to be set up first for access to springs and motionTokens.
Use useSpring for continuous, frame-by-frame updates like cursor followers. Use transition: springs.* for discrete state changes that should trigger on prop updates.
SVG morphing requires equal path command counts. If your paths have different command structures, they will snap. Ensure both paths use the same number and type of commands.
Physics-based motion with useSpring and dragTransition: springs.release feels more natural than duration-based animations. Always test on actual touch devices since feel and thresholds differ from mouse.
No. The scope ref must be attached to a mounted DOM element. Calling animate() before mount throws silently. Ensure the ref is attached and the component is rendered first.
Full instructions (SKILL.md)
Source of truth, from affaan-m/everything-claude-code.
name: motion-advanced description: Advanced motion patterns for React / Next.js — drag & drop, gestures, text animations, SVG path drawing, custom hooks, imperative sequences (useAnimate), loaders, and the full API decision tree. Requires motion-foundations. version: 1.0 tags: [motion, animation, advanced, gestures, svg] category: frontend author: jeff
Motion Advanced
Complex, interactive, and physics-based animation patterns.
Requires motion-foundations to be set up first.
Use these when motion-patterns is not enough.
When to Activate
- Building drag-to-dismiss sheets, swipe gestures, or reorderable lists
- Animating text word-by-word, character-by-character, or as a live counter
- Drawing SVG paths, morphing icons, or animating circular progress
- Writing a custom animation hook (
useScrollReveal, magnetic button, cursor follower) - Sequencing multi-step animations imperatively with
useAnimate - Building spinners, shimmer skeletons, pulse indicators, or loading button states
Outputs
This skill produces:
- Drag interactions: draggable cards, drag-to-dismiss sheets,
Reorder.Grouplists - Gesture hooks: swipe detection, long press, pinch outline
- Text animation components: word reveal, character typewriter, number counter
- SVG animation: path draw-on, icon morph, stroke progress ring
- Custom hooks:
useScrollReveal,useHoverScale,useNavigationDirection,useInViewOnce - Imperative sequences via
useAnimatewith interrupt-safeasync/await - Loader components: spinner, shimmer, pulse dot, progress bar, button loading state
Principles
- Physics-based motion (
useSpring,springs.*) always feels more natural than duration-based for direct manipulation. useMotionValue+useTransformcomputes derived values without triggering re-renders.useAnimatesequences are imperative and interrupt-safe — callinganimate()mid-flight cancels the previous animation automatically.- Motion values (
useMotionValue,useSpring) are SSR-safe and do not cause hydration errors.
Rules
- Drag interactions must be tested on touch devices, not just mouse.
dragprop works on both but feel and threshold differ. - Infinite animations must pause when
document.visibilityState === "hidden". Background tabs must not consume GPU/CPU. - Swipe threshold must be explicit. Never infer intent from velocity alone; combine
offset+velocitychecks. useAnimatescope ref must be attached to a mounted DOM element. Callinganimate()before mount throws silently.- Motion values must not be recreated on render.
useMotionValue(0)inside a component body is correct;new MotionValue(0)in a render is not. - All token values are imported from
motion-foundations. No inline numbers. - Custom hooks must handle cleanup. Every
window.addEventListenerneeds a matchingremoveEventListenerin theuseEffectreturn. - SVG morphing requires equal path command counts. Paths with different command structures snap instead of interpolating.
Decision Guidance
Choosing the right advanced API
| Scenario | API |
|---|---|
| Drag with physics on release | drag + dragTransition: springs.release |
| Ordered drag-to-reorder list | Reorder.Group + Reorder.Item |
| Dismiss on drag offset | drag="y" + onDragEnd offset check |
| Swipe left/right | drag="x" + onDragEnd offset check |
| Long press | useLongPress hook |
| Value smoothed over time | useSpring |
| Value derived from another | useTransform |
| Multi-step sequence | useAnimate with async/await |
| One-shot imperative animation | animate() from motion |
| Text entering word by word | Stagger on inline-block spans |
| SVG drawing on | pathLength 0 → 1 |
| SVG morph | d attribute tween (equal commands) |
| Circular progress | strokeDashoffset tween |
When to use useSpring vs a spring transition
useSpring | transition: springs.* | |
|---|---|---|
| Use for | Cursor follower, pointer-tracked values | Discrete state changes |
| Updates | Continuous, on every frame | Triggered by state change |
| Interrupt | Smooth — physics picks up from velocity | Restarts from current value |
Core Concepts
useMotionValue + useTransform
Reactive computation without re-renders:
const x = useMotionValue(0)
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])
// opacity updates every frame as x changes — no setState, no re-render
useAnimate
Returns [scope, animate]. The scope ref must be attached to a DOM element.
animate() calls are interrupt-safe — calling mid-flight cancels the previous run.
const [scope, animate] = useAnimate()
async function play() {
await animate(".step-1", { opacity: 1 }, { duration: 0.3 })
await animate(".step-2", { x: 0 }, { duration: 0.4 })
animate(".step-3", { scale: 1 }, { duration: 0.25 }) // fire and forget
}
return <div ref={scope}>...</div>
Code Examples
Draggable card
"use client"
import { motion } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
dragElastic={0.1}
whileDrag={{
scale: motionTokens.scale.pop,
boxShadow: "0 16px 40px rgba(0,0,0,0.2)",
}}
dragTransition={springs.release}
/>
Drag-to-dismiss sheet
"use client"
import { motion, useMotionValue, useTransform } from "motion/react"
export function BottomSheet({ onClose }: { onClose: () => void }) {
const y = useMotionValue(0)
const opacity = useTransform(y, [0, 200], [1, 0])
return (
<motion.div
drag="y"
dragConstraints={{ top: 0 }}
style={{ y, opacity }}
onDragEnd={(_, info) => {
// Rule 3: combine offset + velocity
if (info.offset.y > 120 || info.velocity.y > 500) onClose()
}}
/>
)
}
Reorderable list
"use client"
import { Reorder } from "motion/react"
export function SortableList() {
const [items, setItems] = useState(initialItems)
return (
<Reorder.Group axis="y" values={items} onReorder={setItems}>
{items.map((item) => (
<Reorder.Item key={item.id} value={item}>
{item.label}
</Reorder.Item>
))}
</Reorder.Group>
)
}
Swipe detection
"use client"
import { motion } from "motion/react"
const OFFSET_THRESHOLD = 50
const VELOCITY_THRESHOLD = 300
<motion.div
drag="x"
dragConstraints={{ left: 0, right: 0 }}
onDragEnd={(_, info) => {
const swipedRight = info.offset.x > OFFSET_THRESHOLD || info.velocity.x > VELOCITY_THRESHOLD
const swipedLeft = info.offset.x < -OFFSET_THRESHOLD || info.velocity.x < -VELOCITY_THRESHOLD
if (swipedRight) onSwipeRight()
if (swipedLeft) onSwipeLeft()
}}
/>
Long press hook
import { useRef } from "react"
export function useLongPress(callback: () => void, ms = 600) {
const timerRef = useRef<ReturnType<typeof setTimeout>>()
return {
onPointerDown: () => { timerRef.current = setTimeout(callback, ms) },
onPointerUp: () => clearTimeout(timerRef.current),
onPointerLeave: () => clearTimeout(timerRef.current),
}
}
Word-by-word reveal
"use client"
import { motion } from "motion/react"
import { springs } from "@/lib/motion-tokens"
export function AnimatedText({ text }: { text: string }) {
return (
<motion.p
variants={{ visible: { transition: { staggerChildren: 0.05 } } }}
initial="hidden"
animate="visible"
>
{text.split(" ").map((word, i) => (
<motion.span
key={i}
className="inline-block mr-1"
variants={{
hidden: { opacity: 0, y: 12 },
visible: { opacity: 1, y: 0, transition: springs.gentle },
}}
>
{word}
</motion.span>
))}
</motion.p>
)
}
Number counter
"use client"
import { useRef, useEffect } from "react"
import { animate } from "motion"
import { motionTokens } from "@/lib/motion-tokens"
export function Counter({ to }: { to: number }) {
const nodeRef = useRef<HTMLSpanElement>(null)
useEffect(() => {
const controls = animate(0, to, {
duration: motionTokens.duration.crawl,
ease: motionTokens.easing.smooth,
onUpdate: (v) => {
if (nodeRef.current) nodeRef.current.textContent = Math.round(v).toString()
},
})
return controls.stop // Rule 7: cleanup
}, [to])
return <span ref={nodeRef} />
}
SVG path draw-on
"use client"
import { motion } from "motion/react"
import { motionTokens } from "@/lib/motion-tokens"
<motion.path
d="M 0 100 Q 50 0 100 100"
initial={{ pathLength: 0, opacity: 0 }}
animate={{ pathLength: 1, opacity: 1 }}
transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
/>
Stroke progress ring
"use client"
import { motion } from "motion/react"
import { motionTokens } from "@/lib/motion-tokens"
const CIRCUMFERENCE = 2 * Math.PI * 40 // r=40
export function ProgressRing({ progress }: { progress: number }) {
return (
<svg width="100" height="100" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="none" stroke="#e5e7eb" strokeWidth="8" />
<motion.circle
cx="50" cy="50" r="40"
fill="none" stroke="#6366f1" strokeWidth="8"
strokeLinecap="round"
strokeDasharray={CIRCUMFERENCE}
animate={{ strokeDashoffset: CIRCUMFERENCE - (progress / 100) * CIRCUMFERENCE }}
transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
style={{ rotate: -90, transformOrigin: "center" }}
/>
</svg>
)
}
useScrollReveal hook
"use client"
import { useRef } from "react"
import { useScroll, useTransform } from "motion/react"
import { motionTokens } from "@/lib/motion-tokens"
export function useScrollReveal() {
const ref = useRef(null)
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] })
const opacity = useTransform(scrollYProgress, [0, 0.3], [0, 1])
const y = useTransform(scrollYProgress, [0, 0.3], [motionTokens.distance.lg, 0])
return { ref, style: { opacity, y } }
}
// Usage
const { ref, style } = useScrollReveal()
<motion.section ref={ref} style={style} />
Cursor follower
"use client"
import { useEffect } from "react"
import { motion, useMotionValue, useSpring } from "motion/react"
import { springs } from "@/lib/motion-tokens"
export function CursorFollower() {
const x = useMotionValue(-100)
const y = useMotionValue(-100)
const sx = useSpring(x, springs.gentle)
const sy = useSpring(y, springs.gentle)
useEffect(() => {
const move = (e: MouseEvent) => { x.set(e.clientX); y.set(e.clientY) }
window.addEventListener("mousemove", move)
return () => window.removeEventListener("mousemove", move) // Rule 7
}, [])
return (
<motion.div
className="fixed top-0 left-0 w-6 h-6 rounded-full bg-indigo-500
pointer-events-none -translate-x-1/2 -translate-y-1/2 z-50"
style={{ x: sx, y: sy }}
/>
)
}
Shimmer skeleton
"use client"
import { useEffect } from "react"
import { motion, useAnimation } from "motion/react"
import { motionTokens } from "@/lib/motion-tokens"
export function ShimmerSkeleton({ className = "" }: { className?: string }) {
const controls = useAnimation()
useEffect(() => {
const play = () =>
controls.start({
x: ["-100%", "100%"],
transition: {
repeat: Infinity,
duration: motionTokens.duration.crawl,
ease: motionTokens.easing.linear,
},
})
const handleVisibility = () => {
if (document.visibilityState === "hidden") controls.stop()
else void play()
}
void play()
document.addEventListener("visibilitychange", handleVisibility)
return () => {
controls.stop()
document.removeEventListener("visibilitychange", handleVisibility)
}
}, [controls])
return (
<div className={`relative overflow-hidden bg-gray-200 rounded ${className}`}>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent"
initial={{ x: "-100%" }}
animate={controls}
/>
</div>
)
}
Button loading state
"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
export function LoadingButton({
loading,
label,
onClick,
}: {
loading: boolean
label: string
onClick: () => void
}) {
return (
<motion.button
onClick={onClick}
animate={{ opacity: loading ? 0.7 : 1 }}
whileTap={loading ? {} : { scale: motionTokens.scale.press }}
transition={springs.snappy}
disabled={loading}
>
<AnimatePresence mode="wait">
{loading ? (
<motion.span
key="loading"
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
transition={{ duration: motionTokens.duration.fast }}
>
…
</motion.span>
) : (
<motion.span
key="label"
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
transition={{ duration: motionTokens.duration.fast }}
>
{label}
</motion.span>
)}
</AnimatePresence>
</motion.button>
)
}
Infinite animation with visibility pause
"use client"
import { useEffect } from "react"
import { motion, useAnimation } from "motion/react"
import { motionTokens } from "@/lib/motion-tokens"
export function PulseDot() {
const controls = useAnimation()
useEffect(() => {
const pulse = () =>
controls.start({
scale: [1, 1.4, 1],
opacity: [1, 0.6, 1],
transition: { repeat: Infinity, duration: motionTokens.duration.crawl },
})
// Rule 2: pause when tab is hidden
const handleVisibility = () => {
if (document.visibilityState === "hidden") controls.stop()
else void pulse()
}
void pulse()
document.addEventListener("visibilitychange", handleVisibility)
// Rule 7: stop controls and remove listeners on unmount.
return () => {
controls.stop()
document.removeEventListener("visibilitychange", handleVisibility)
}
}, [controls])
return <motion.span className="w-2 h-2 rounded-full bg-green-400" animate={controls} />
}
End-to-End Example
Drag-to-dismiss sheet with shimmer content, loading state, and reduced motion
support — combining useMotionValue, useTransform, useSafeMotion,
AnimatePresence, and tokens from motion-foundations:
"use client"
import { useState } from "react"
import { motion, AnimatePresence, useMotionValue, useTransform } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"
import { ShimmerSkeleton } from "./shimmer-skeleton"
export function DismissibleSheet({
isOpen,
onClose,
loading,
children,
}: {
isOpen: boolean
onClose: () => void
loading: boolean
children: React.ReactNode
}) {
const safe = useSafeMotion(motionTokens.distance.xl)
const y = useMotionValue(0)
const opacity = useTransform(y, [0, 200], [1, 0])
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
key="backdrop"
className="fixed inset-0 bg-black/40"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
/>
{/* Sheet — drag-to-dismiss */}
<motion.div
key="sheet"
className="fixed bottom-0 inset-x-0 rounded-t-2xl bg-white p-6"
drag="y"
dragConstraints={{ top: 0 }}
style={{ y, opacity }}
onDragEnd={(_, info) => {
if (info.offset.y > 120 || info.velocity.y > 500) onClose()
}}
initial={safe.initial}
animate={safe.animate}
exit={safe.exit}
transition={springs.gentle}
>
{loading ? (
<div className="space-y-3">
<ShimmerSkeleton className="h-4 w-3/4" />
<ShimmerSkeleton className="h-4 w-1/2" />
<ShimmerSkeleton className="h-20 w-full" />
</div>
) : children}
</motion.div>
</>
)}
</AnimatePresence>
)
}
Constraints / Non-Goals
This skill does not cover:
- Token and spring definitions → see
motion-foundations - Standard UI patterns (button, modal, stagger, page transitions) → see
motion-patterns - CSS-only animations or Tailwind
animate-*withoutmotion/react - Canvas or WebGL-based animation (Three.js, Pixi, etc.)
- Full drag-and-drop systems with external state managers (dnd-kit, react-beautiful-dnd)
- Game-loop or frame-by-frame animation
Anti-Patterns
| Anti-pattern | Rule violated | Fix |
|---|---|---|
drag tested only on desktop | Rule 1 | Test on touch emulator and real device |
animate={{ repeat: Infinity }} with no pause | Rule 2 | Add visibilitychange listener |
onDragEnd checking only offset, not velocity | Rule 3 | Check both info.offset and info.velocity |
animate(scope, ...) before useEffect | Rule 4 | Call animate() only after mount |
const x = new MotionValue(0) in render | Rule 5 | Use const x = useMotionValue(0) |
transition={{ duration: 1.2 }} inline | Rule 6 | Use motionTokens.duration.crawl |
useEffect without cleanup | Rule 7 | Return removeEventListener / controls.stop |
| SVG morph between paths with different commands | Rule 8 | Normalize path commands before animating |
Related Skills
motion-foundations— defines all tokens, springs,useSafeMotion, and SSR guards imported here. Must be set up before using this skill.motion-patterns— handles standard UI patterns (button, modal, stagger, page transitions, scroll reveals). Use it before reaching for the advanced patterns here.
Related skills
More from affaan-m/everything-claude-code and the wider catalog.

motion-foundations
Foundation layer for React/Next.js animations: tokens, springs, accessibility rules, and SSR safety using motion/react.

motion-patterns
Production-ready animation patterns for React/Next.js built on motion-foundations tokens and springs.

motion-ui
Production-ready UI motion system for React/Next.js with performance, accessibility, and usability focus.

mysql-patterns
MySQL and MariaDB schema, indexing, transaction, and connection-pool patterns for production backends.

nanoclaw-repl
Zero-dependency session-aware REPL for Claude Code with persistent markdown storage and dynamic skill loading.

nestjs-patterns
Production-grade NestJS patterns for modular TypeScript backends with validation, guards, and config.