/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.
$ npx -y skills add affaan-m/ECC --skill motion-patterns --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/motion-patterns
Context preview
The summary Claude sees to decide when to auto-load this skill.
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.
SKILL.md
motion-patterns.SKILL.mdname: motion-patterns
description: 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.
version: 1.0
tags: [motion, animation, ui-patterns]
category: frontend
author: jeff
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
| Situation | Pattern | | ---------------------------------------- | ---------------------- | | Element appears / disappears | `AnimatePresence` | | List of items loading in sequence | Stagger variants | | Navigating between routes | Page transition wrapper| | Element changes size in place | `layout` prop | | Same element moves across page contexts | `layoutId` | | Element enters when scrolled into view | `whileInView` | | Value tied to scroll position | `useScroll` + `useTransform` |
When to use `mode="wait"` vs `mode="sync"`
| Mode | Use when | | ------- | --------------------------------------- | | `wait` | Page transitions, content swaps (one at a time) | | `sync` | Stacked notifications, list items (overlap is fine) | | `popLayout` | Items 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:Read more
name: motion-patterns description: 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. version: 1.0 tags: [motion, animation, ui-patterns] category: frontend author: jeff
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
| Situation | Pattern | | ---------------------------------------- | ---------------------- | | Element appears / disappears | `AnimatePresence` | | List of items loading in sequence | Stagger variants | | Navigating between routes | Page transition wrapper| | Element changes size in place | `layout` prop | | Same element moves across page contexts | `layoutId` | | Element enters when scrolled into view | `whileInView` | | Value tied to scroll position | `useScroll` + `useTransform` |
When to use `mode="wait"` vs `mode="sync"`
| Mode | Use when | | ------- | --------------------------------------- | | `wait` | Page transitions, content swaps (one at a time) | | `sync` | Stacked notifications, list items (overlap is fine) | | `popLayout` | Items 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:Your agent can write code, but ECC gives it a coordinated engineering system and toolbox: it plans before it builds, verifies changes with tests, reviews its own work from a fresh context, remembers what matters, and turns repeated wins into reusable skills
Repo: affaan-m/ECC
Other skills on ecc.
- /everything-claude-code
Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.
Open skill - /accessibility
Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA
Open skill - /agent-architecture-audit
Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for
Open skill - /agent-eval
Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics
Open skill - /agent-harness-construction
Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates.
Open skill - /agent-introspection-debugging
Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports.
Open skill

