Skip to content
motion-advanced logo

Motion Advanced

motion-advanced

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.

affaan-m/ecc5.9Kinstalls273.3Kstars

SKILL.md

Full skill instructions

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.Group lists
  • 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 useAnimate with interrupt-safe async/​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 + useTransform computes derived values without triggering re-renders.
  • useAnimate sequences are imperative and interrupt-safe — calling animate() mid-flight cancels the previous animation automatically.
  • Motion values (useMotionValue, useSpring) are SSR-safe and do not cause hydration errors.

Rules

  1. Drag interactions must be tested on touch devices, not just mouse. drag prop works on both but feel and threshold differ.
  2. Infinite animations must pause when document.visibilityState === "hidden". Background tabs must not consume GPU/​CPU.
  3. Swipe threshold must be explicit. Never infer intent from velocity alone; combine offset + velocity checks.
  4. useAnimate scope ref must be attached to a mounted DOM element. Calling animate() before mount throws silently.
  5. Motion values must not be recreated on render. useMotionValue(0) inside a component body is correct; new MotionValue(0) in a render is not.
  6. All token values are imported from motion-foundations. No inline numbers.
  7. Custom hooks must handle cleanup. Every window.addEventListener needs a matching removeEventListener in the useEffect return.
  8. SVG morphing requires equal path command counts. Paths with different command structures snap instead of interpolating.

Decision Guidance

Choosing the right advanced API

ScenarioAPI
Drag with physics on releasedrag + dragTransition: springs.release
Ordered drag-to-reorder listReorder.Group + Reorder.Item
Dismiss on drag offsetdrag="y" + onDragEnd offset check
Swipe left/​rightdrag="x" + onDragEnd offset check
Long pressuseLongPress hook
Value smoothed over timeuseSpring
Value derived from anotheruseTransform
Multi-step sequenceuseAnimate with async/​await
One-shot imperative animationanimate() from motion
Text entering word by wordStagger on inline-block spans
SVG drawing onpathLength 0 → 1
SVG morphd attribute tween (equal commands)
Circular progressstrokeDashoffset tween

When to use useSpring vs a spring transition

useSpringtransition: springs.*
Use forCursor follower, pointer-tracked valuesDiscrete state changes
UpdatesContinuous, on every frameTriggered by state change
InterruptSmooth — physics picks up from velocityRestarts 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-* without motion/​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-patternRule violatedFix
drag tested only on desktopRule 1Test on touch emulator and real device
animate={{ repeat: Infinity }} with no pauseRule 2Add visibilitychange listener
onDragEnd checking only offset, not velocityRule 3Check both info.offset and info.velocity
animate(scope, ...) before useEffectRule 4Call animate() only after mount
const x = new MotionValue(0) in renderRule 5Use const x = useMotionValue(0)
transition={{ duration: 1.2 }} inlineRule 6Use motionTokens.duration.crawl
useEffect without cleanupRule 7Return removeEventListener / controls.stop
SVG morph between paths with different commandsRule 8Normalize 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.

More skills from affaan-m

coding-standards logo
affaan-m/ecc

coding-standards

Baseline cross-project coding conventions for naming, readability, immutability, and code-quality review. Use detailed frontend or backend skills for framework-specific patterns.

273.3K 14K
View
golang-testing logo
affaan-m/ecc

golang-testing

Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.

273.3K 13.1K
View
docker-patterns logo
affaan-m/ecc

docker-patterns

Docker and Docker Compose patterns for local development, container security, networking, volume strategies, and multi-service orchestration.

273.3K 12.7K
View

Spring Boot architecture patterns, REST API design, layered services, data access, caching, async processing, and logging. Use for Java Spring Boot backend work.

273.3K 12.2K
View
video-editing logo
affaan-m/ecc

video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

273.3K 12.2K
View
python-patterns logo
affaan-m/ecc

python-patterns

Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.

273.3K 12.1K
View
frontend-slides logo
affaan-m/ecc

frontend-slides

Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to build a presentation, convert a PPT/PPTX to web, or create slides for a talk/pitch. Helps non-designers discover their aesthetic through visual exploration rather than abstract choices.

273.3K 12.1K
View
api-design logo
affaan-m/ecc

api-design

REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs.

273.3K 11.9K
View
django-patterns logo
affaan-m/ecc

django-patterns

Django architecture patterns, REST API design with DRF, ORM best practices, caching, signals, middleware, and production-grade Django apps.

273.3K 11.9K
View
postgres-patterns logo
affaan-m/ecc

postgres-patterns

PostgreSQL database patterns for query optimization, schema design, indexing, and security. Based on Supabase best practices.

273.3K 11.9K
View
tdd-workflow logo
affaan-m/ecc

tdd-workflow

Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests.

273.3K 11.6K
View
prompt-optimizer logo
affaan-m/ecc

prompt-optimizer

Analyze raw prompts, identify intent and gaps, match ECC components (skills/commands/agents/hooks), and output a ready-to-paste optimized prompt. Advisory role only — never executes the task itself. TRIGGER when: user says "optimize prompt", "improve my prompt", "how to write a prompt for", "help me prompt", "rewrite this prompt", or explicitly asks to enhance prompt quality. Also triggers on Chinese equivalents: "优化prompt", "改进prompt", "怎么写prompt", "帮我优化这个指令". DO NOT TRIGGER when: user wants the task executed directly, or says "just do it" / "直接做". DO NOT TRIGGER when user says "优化代码", "优化性能", "optimize performance", "optimize this code" — those are refactoring/performance tasks, not prompt optimization.

273.3K 11.3K
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View