npx skills add ...
npx skills add affaan-m/ecc --skill 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.
npx skills add affaan-m/ecc --skill 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.
This skill produces:
AnimatePresence-wrapped conditional renders with correct exit behavioruseScroll + useTransformlayout, layoutId) for expanding and crossfading elementsmotion-foundations. No raw numbers.AnimatePresence with a key.layout is used only for small, isolated shifts. Large subtrees get explicit transforms.AnimatePresence with a key on the direct child. Without a key, exit animations never fire.exit when defining initial + animate. An animation without an exit is incomplete.mode="wait" on page transitions. Enter must not start until exit completes.layout on subtrees with more than ~5 children or deeply nested DOM. Use explicit x/y transforms instead.0.05s and 0.10s. Below feels mechanical; above feels sluggish.role="dialog", aria-modal="true".viewport={{ once: true }}. Repeating on scroll-out is distracting, not informative.motion-foundations. No inline numbers.| 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 |
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 |
Three things must always be true:
AnimatePresence wraps the conditionalkeyexit propMiss any one of these and the exit animation silently fails.
layout — animates the element's own size/position change in placelayoutId — links two separate elements, crossfading between them across rendersUse layout="position" on text inside an expanding container to prevent text reflow from animating.
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:
This skill does not cover:
motion-foundationsmotion-advancedmotion-advancedmotion-advancedmotion-advancedmotion/react| Anti-pattern | Rule violated | Fix |
|---|---|---|
AnimatePresence child missing key | Rule 1 | Add stable key to the direct child |
initial + animate without exit | Rule 2 | Always define all three together |
Page transition without mode="wait" | Rule 3 | Add mode="wait" to AnimatePresence |
layout on a 50-item list | Rule 4 | Use mode="popLayout" or explicit transforms |
staggerChildren: 0.2 on a 10-item list | Rule 5 | Cap at 0.08–0.10 |
| Modal without focus trap | Rule 6 | Add focus-trap-react or Radix Dialog |
whileInView without viewport={{ once: true }} | Rule 7 | Repeating entrances distract, not inform |
transition={{ duration: 0.3 }} inline | Rule 8 | Use motionTokens.duration.normal |
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.