Source profileQuality 90/100

affaan-m/ECC/skills/motion-patterns/SKILL.md

motion-patterns

Production-ready animation patterns for React / Next.js — button, modal, toast, stagger, page transitions, exit animations, scroll, and layout — built on motion-foundations tokens and springs. Use when animating a specific UI element in React or Next.js — button, modal, toast, stagger, page transition, or scroll.

Source repository stars
242,963
Declared platforms
0
Static risk flags
0
Last source update
2026-08-25
Source checked
2026-08-25

Decision brief

What it does: where it fits

Copy-paste patterns for the most common UI animation needs. Every pattern here is built on motion-foundations tokens and springs. Do not define new duration or easing values here — import them.

Best for

  • Use when animating a specific UI element in React or Next.

Not for

  • Tasks that require unconfirmed production actions or broad system permissions.
  • Environments where the pinned source and install steps cannot be inspected.

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/affaan-m/ECC --skill "skills/motion-patterns"
Safe inspection promptEditorial

Inspect the Agent Skill "motion-patterns" from https://github.com/affaan-m/ECC/blob/d8409a4b0813771235555e32e3d8046a73988bfa/skills/motion-patterns/SKILL.md at commit d8409a4b0813771235555e32e3d8046a73988bfa. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    When to Activate

    Animating a button, card, modal, or toast notification

    Animating a button, card, modal, or toast notificationBuilding list entrances with staggerSetting up page transitions in Next.js App Router
  2. 02

    Outputs

    Accessible, SSR-safe animation for all standard UI components

    Accessible, SSR-safe animation for all standard UI componentsAnimatePresence-wrapped conditional renders with correct exit behaviorPage transition wrapper component for Next.js App Router
  3. 03

    Principles

    Every pattern imports from motion-foundations. No raw numbers.

    Every pattern imports from motion-foundations. No raw numbers.Every conditional render is wrapped in AnimatePresence with a key.Exit animations are always defined alongside enter animations — never as an afterthought.
  4. 04

    Rules

    1. Always wrap conditional renders in AnimatePresence with a key on the direct child. Without a key, exit animations never fire. 2. Always define exit when defining initial + animate. An animation without an exit is incomplete. 3. Use mode="wait" on page transitions. Enter must…

    Always wrap conditional renders in AnimatePresence with a key on the direct child. Without a key, exit animations never fire.Always define exit when defining initial + animate. An animation without an exit is incomplete.Use mode="wait" on page transitions. Enter must not start until exit completes.
  5. 05

    Decision Guidance

    Review the “Decision Guidance” section in the pinned source before continuing.

    Review and apply the “Decision Guidance” source section.

Permission review

Static risk signals and limitations

No configured static risk pattern was detected

This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score90/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars242,963SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
affaan-m/ECC
Skill path
skills/motion-patterns/SKILL.md
Commit
d8409a4b0813771235555e32e3d8046a73988bfa
License
MIT
Collected
2026-08-25
Default branch
main
View the original SKILL.md

Motion Patterns

Copy-paste patterns for the most common UI animation needs. Every pattern here is built on motion-foundations tokens and springs. Do not define new duration or easing values here — import them.

When to Activate

  • Animating a button, card, modal, or toast notification
  • Building list entrances with stagger
  • Setting up page transitions in Next.js App Router
  • Adding entrance or exit animations to conditional content
  • Implementing scroll-reveal, scroll-linked progress, or sticky story sections
  • Building expanding cards, accordions, or shared-element transitions

Outputs

This skill produces:

  • Accessible, SSR-safe animation for all standard UI components
  • AnimatePresence-wrapped conditional renders with correct exit behavior
  • Page transition wrapper component for Next.js App Router
  • Scroll-reveal and scroll-linked patterns using useScroll + useTransform
  • Layout animation patterns (layout, layoutId) for expanding and crossfading elements

Principles

  • Every pattern imports from motion-foundations. No raw numbers.
  • Every conditional render is wrapped in AnimatePresence with a key.
  • Exit animations are always defined alongside enter animations — never as an afterthought.
  • layout is used only for small, isolated shifts. Large subtrees get explicit transforms.

Rules

  1. Always wrap conditional renders in AnimatePresence with a key on the direct child. Without a key, exit animations never fire.
  2. Always define exit when defining initial + animate. An animation without an exit is incomplete.
  3. Use mode="wait" on page transitions. Enter must not start until exit completes.
  4. Never use layout on subtrees with more than ~5 children or deeply nested DOM. Use explicit x/y transforms instead.
  5. Stagger interval must stay between 0.05s and 0.10s. Below feels mechanical; above feels sluggish.
  6. Modals must always include: focus trap, Escape-key close, scroll lock, role="dialog", aria-modal="true".
  7. Scroll reveals use viewport={{ once: true }}. Repeating on scroll-out is distracting, not informative.
  8. All token values are imported from motion-foundations. No inline numbers.

Decision Guidance

Choosing the right pattern

SituationPattern
Element appears / disappearsAnimatePresence
List of items loading in sequenceStagger variants
Navigating between routesPage transition wrapper
Element changes size in placelayout prop
Same element moves across page contextslayoutId
Element enters when scrolled into viewwhileInView
Value tied to scroll positionuseScroll + useTransform

When to use mode="wait" vs mode="sync"

ModeUse when
waitPage transitions, content swaps (one at a time)
syncStacked notifications, list items (overlap is fine)
popLayoutItems removed from a reflow list

Core Concepts

AnimatePresence contract

Three things must always be true:

  1. AnimatePresence wraps the conditional
  2. The direct child has a key
  3. The child has an exit prop

Miss any one of these and the exit animation silently fails.

layout vs layoutId

  • layout — animates the element's own size/position change in place
  • layoutId — links two separate elements, crossfading between them across renders

Use layout="position" on text inside an expanding container to prevent text reflow from animating.

Code Examples

Button feedback

"use client"
import { motion } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

<motion.button
  whileHover={{ scale: motionTokens.scale.pop }}
  whileTap={{ scale: motionTokens.scale.press }}
  transition={springs.snappy}
/>

Stagger list

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

const container = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.08,   // within the 0.05–0.10 rule
      delayChildren: 0.1,
    },
  },
}

const item = {
  hidden:  { opacity: 0, y: motionTokens.distance.md },
  visible: { opacity: 1, y: 0, transition: springs.gentle },
}

<motion.ul variants={container} initial="hidden" animate="visible">
  {items.map((i) => (
    <motion.li key={i.id} variants={item} />
  ))}
</motion.ul>

Modal

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

// Wrap at the call site:
// <AnimatePresence>{isOpen && <Modal key="modal" />}</AnimatePresence>

export function Modal({ onClose }: { onClose: () => void }) {
  return (
    <>
      {/* Overlay */}
      <motion.div
        className="fixed inset-0 bg-black/50"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onClick={onClose}
      />

      {/* Panel — accessibility requirements: focus trap, Escape close,
          scroll lock, role="dialog", aria-modal="true" */}
      <motion.div
        role="dialog"
        aria-modal="true"
        className="fixed inset-x-4 top-1/2 -translate-y-1/2 rounded-xl bg-white p-6"
        initial={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        animate={{ opacity: 1, scale: 1, y: 0 }}
        exit={{
          opacity: 0,
          scale: motionTokens.scale.press,
          y: motionTokens.distance.sm,
        }}
        transition={springs.gentle}
      />
    </>
  )
}

Toast stack

"use client"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<AnimatePresence mode="sync">
  {toasts.map((t) => (
    <motion.div
      key={t.id}
      layout
      initial={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      animate={{ opacity: 1, x: 0, scale: 1 }}
      exit={{
        opacity: 0,
        x: motionTokens.distance.xl,
        scale: motionTokens.scale.subtle,
      }}
      transition={springs.snappy}
    />
  ))}
</AnimatePresence>

Page transition (Next.js App Router)

// components/page-transition.tsx
"use client"
import { motion, AnimatePresence } from "motion/react"
import { usePathname } from "next/navigation"
import { motionTokens } from "@/lib/motion-tokens"

const variants = {
  initial: { opacity: 0, y: motionTokens.distance.sm },
  enter:   { opacity: 1, y: 0 },
  exit:    { opacity: 0, y: -motionTokens.distance.sm },
}

export function PageTransition({ children }: { children: React.ReactNode }) {
  const pathname = usePathname()
  return (
    <AnimatePresence mode="wait">
      <motion.div
        key={pathname}
        variants={variants}
        initial="initial"
        animate="enter"
        exit="exit"
        transition={{
          duration: motionTokens.duration.normal,
          ease: motionTokens.easing.smooth,
        }}
      >
        {children}
      </motion.div>
    </AnimatePresence>
  )
}

Scroll reveal

"use client"
import { motion } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"

<motion.div
  initial={{ opacity: 0, y: motionTokens.distance.lg }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-80px" }}   // once: true — rule 7
  transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
/>

Scroll progress bar

"use client"
import { motion, useScroll } from "motion/react"

export function ScrollProgress() {
  const { scrollYProgress } = useScroll()
  return (
    <motion.div
      className="fixed top-0 left-0 h-1 bg-indigo-500 origin-left w-full"
      style={{ scaleX: scrollYProgress }}
    />
  )
}

Expanding card

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { springs, motionTokens } from "@/lib/motion-tokens"

export function ExpandingCard({ title, body }: { title: string; body: string }) {
  const [expanded, setExpanded] = useState(false)

  return (
    <motion.div layout onClick={() => setExpanded(!expanded)} className="cursor-pointer">
      {/* layout="position" prevents text reflow from animating */}
      <motion.h2 layout="position" className="font-semibold">
        {title}
      </motion.h2>

      <AnimatePresence>
        {expanded && (
          <motion.p
            key="body"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: motionTokens.duration.fast }}
          >
            {body}
          </motion.p>
        )}
      </AnimatePresence>
    </motion.div>
  )
}

Shared-element crossfade

// Source context
<motion.img layoutId="hero-image" src={src} className="w-16 h-16 rounded" />

// Destination context (same layoutId — motion handles the transition)
<motion.img layoutId="hero-image" src={src} className="w-full rounded-xl" />

Accordion

<motion.div
  initial={false}
  animate={{ opacity: open ? 1 : 0, scaleY: open ? 1 : 0 }}
  style={{ transformOrigin: "top", overflow: "hidden" }}
  transition={{
    duration: motionTokens.duration.normal,
    ease: motionTokens.easing.smooth,
  }}
> {children}
</motion.div>

End-to-End Example

A staggered list that enters on mount, handles conditional presence, and respects reduced motion — combining tokens, springs, AnimatePresence, and the accessibility hook from motion-foundations:

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "motion/react"
import { motionTokens, springs } from "@/lib/motion-tokens"
import { useSafeMotion } from "@/hooks/use-reduced-motion"

const containerVariants = {
  hidden: {},
  visible: {
    transition: { staggerChildren: 0.08, delayChildren: 0.1 },
  },
}

function ListItem({ label, onRemove }: { label: string; onRemove: () => void }) {
  const safe = useSafeMotion(motionTokens.distance.sm)
  return (
    <motion.li
      variants={{
        hidden:  safe.initial,
        visible: safe.animate,
      }}
      exit={safe.exit}
      transition={springs.gentle}
      className="flex items-center justify-between p-3 rounded-lg bg-white shadow-sm"
    >
      <span>{label}</span>
      <button onClick={onRemove}>Remove</button>
    </motion.li>
  )
}

export function AnimatedList({ items, onRemove }: {
  items: { id: string; label: string }[]
  onRemove: (id: string) => void
}) {
  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="visible"
      className="space-y-2"
    >
      <AnimatePresence mode="popLayout">
        {items.map((item) => (
          <ListItem
            key={item.id}
            label={item.label}
            onRemove={() => onRemove(item.id)}
          />
        ))}
      </AnimatePresence>
    </motion.ul>
  )
}

Constraints / Non-Goals

This skill does not cover:

  • Token and spring definitions → see motion-foundations
  • Drag interactions, swipe gestures, reorderable lists → see motion-advanced
  • Text animations (word/character reveal, counters) → see motion-advanced
  • SVG path drawing or morphing → see motion-advanced
  • Custom animation hooks → see motion-advanced
  • CSS-only transitions not using motion/react

Anti-Patterns

Anti-patternRule violatedFix
AnimatePresence child missing keyRule 1Add stable key to the direct child
initial + animate without exitRule 2Always define all three together
Page transition without mode="wait"Rule 3Add mode="wait" to AnimatePresence
layout on a 50-item listRule 4Use mode="popLayout" or explicit transforms
staggerChildren: 0.2 on a 10-item listRule 5Cap at 0.08–0.10
Modal without focus trapRule 6Add focus-trap-react or Radix Dialog
whileInView without viewport={{ once: true }}Rule 7Repeating entrances distract, not inform
transition={{ duration: 0.3 }} inlineRule 8Use motionTokens.duration.normal

Related Skills

  • motion-foundations — defines all tokens, springs, the useSafeMotion hook, and SSR guards that every pattern here imports. Must be set up first.
  • motion-advanced — extends these patterns with drag, gestures, SVG, text, custom hooks, and imperative sequencing. Does not redefine any patterns from this skill.

Frequently asked questions

What to verify before installation and use

What does the motion-patterns source document cover?

Copy-paste patterns for the most common UI animation needs. Every pattern here is built on motion-foundations tokens and springs. Do not define new duration or easing values here — import them.

How do I install motion-patterns?

The source record exposes this install command: npx skills add https://github.com/affaan-m/ECC --skill "skills/motion-patterns". Inspect the command and pinned source before running it.

Alternatives

Compare before choosing

Computed 961,101

fcakyon/claude-codex-settings

vercel-react-view-transitions

Guide for implementing smooth, native-feeling animations using React's View Transition API (`<ViewTransition>` component, `addTransitionType`, and CSS view transition pseudo-elements). Use this skill whenever the user wants to add page transitions, animate route changes, create shared element animations, animate enter/exit of components, animate list reorder, implement directional (forward/back) navigation animations, or integrate view transitions in Next.js. Also use when the user mentions view

Computed 96223

yonatangross/orchestkit

json-render-catalog

json-render component catalog patterns for AI-safe generative UI. Define Zod-typed catalogs that constrain what AI can generate, use @json-render/shadcn for 36 pre-built components, optimize specs with YAML mode, and apply the three edit modes (patch/merge/diff) for progressive updates. Use when building AI-generated UIs, defining component catalogs, or integrating json-render into React/Vue/Svelte/React Native/Ink/Next.js projects.

Computed 96204

theBGuy/GitDesktop

vercel-react-view-transitions

Guide for implementing smooth, native-feeling animations using React's View Transition API (`<ViewTransition>` component, `addTransitionType`, and CSS view transition pseudo-elements). Use this skill whenever the user wants to add page transitions, animate route changes, create shared element animations, animate enter/exit of components, animate list reorder, implement directional (forward/back) navigation animations, or integrate view transitions in Next.js. Also use when the user mentions view

Computed 95528

vibeeval/vibecosystem

frontend-dev

Full-stack frontend development combining premium UI design, cinematic animations, AI-generated media assets, persuasive copywriting, and visual art. Builds complete, visually striking web pages with real media, advanced motion, and compelling copy. Use when: building landing pages, marketing sites, product pages, dashboards, generating media assets (image/video/audio/music), writing conversion copy, creating generative art, or implementing cinematic scroll animations.