agentleFS
dylanfeltusdylanfeltus/skills/motion-design-patterns/SKILL.md

motion-design-patterns

Skill179 starsChanged 9 months agoMITView on GitHub

Framer Motion (Motion) animation patterns for React — springs, staggers, layout animations, micro-interactions, scroll effects, and page transitions. Use when building or improving UI animations, adding polish, or making interfaces feel premium.

  • Installs packages

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 6 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Motion should be purposeful: Every animation should communicate something — state change, hierarchy, spatial relationship, or feedback.

    line 20
  2. Distinctive:

    Consistency matters: Use the same spring configs throughout a project.

    line 23
  3. Very specific:

    Anti-pattern: Don't use y: 100 or large values — subtle (12–24px) feels premium, large feels janky.

    line 91
  4. Distinctive:

    Anti-pattern: Don't stagger more than ~15 items individually — it feels slow.

    line 134
  5. Very specific:

    Performance tip: Add layout="position" if you only need to animate position (not size).

    line 167
  6. Distinctive:

    Anti-pattern: Don't scale buttons more than 1.05 — it looks cartoonish.

    line 223
  7. Distinctive:

    When building animated components, reference these for patterns and inspiration:

    line 314
  8. Distinctive:

    When a user wants a specific animated component (text reveal, animated border, gradient animation, etc.), check these libraries first — …

    line 320

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Motion Design Patterns

Framer Motion (Motion) patterns for React — springs, staggers, layout animations, micro-interactions, scroll-triggered effects, and exit animations. The #1 differentiator between generic and polished UI.

When to Use

  • Building or improving UI animations in a React project
  • User asks for "polish", "delight", "micro-interactions", or "make it feel good"
  • Adding entrance/exit animations, hover effects, or page transitions
  • Making lists, cards, modals, or navigation feel premium
  • User references Magic UI, Motion Primitives, or Framer Motion

Core Philosophy

  • Motion should be purposeful. Every animation should communicate something — state change, hierarchy, spatial relationship, or feedback.
  • Less is more. One well-tuned spring beats five competing animations.
  • Performance first. Animate transform and opacity only. Never animate width, height, top, left, or margin.
  • Consistency matters. Use the same spring configs throughout a project.

Dependencies

npm install motion

Import: import { motion, AnimatePresence, stagger, useScroll, useTransform } from "motion/react"

Note: The package was renamed from framer-motion to motion in late 2024. Both work, but motion is the current package.


Spring Configurations

Springs feel more natural than easing curves. Use these as your defaults:

Recommended Defaults

// Snappy — buttons, toggles, small elements
const snappy = { type: "spring", stiffness: 500, damping: 30 }

// Smooth — cards, panels, modals
const smooth = { type: "spring", stiffness: 300, damping: 25 }

// Gentle — page transitions, large elements
const gentle = { type: "spring", stiffness: 200, damping: 20 }

// Bouncy — playful UI, notifications, badges
const bouncy = { type: "spring", stiffness: 400, damping: 15 }

Quick Reference

FeelstiffnessdampingUse for
Snappy50030Buttons, toggles, chips
Smooth30025Cards, panels, modals
Gentle20020Page transitions, heroes
Bouncy40015Notifications, badges, fun UI

Rules of Thumb

  • Higher stiffness = faster animation
  • Lower damping = more bounce
  • damping ratio < 1 = will overshoot (bounce)
  • For no bounce, set damping ≥ 2 × √stiffness

Pattern 1: Fade + Rise Entrance

The bread-and-butter entrance animation. Element fades in while sliding up slightly.

<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ type: "spring", stiffness: 300, damping: 25 }}
>
  Content here
</motion.div>

When to use: Cards, sections, any content appearing on the page.

Anti-pattern: Don't use y: 100 or large values — subtle (12–24px) feels premium, large feels janky.


Pattern 2: Staggered List

Children animate in one after another. The cascade effect that makes lists feel alive.

const container = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.08,
      delayChildren: 0.1,
    },
  },
}

const item = {
  hidden: { opacity: 0, y: 16 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { type: "spring", stiffness: 300, damping: 25 },
  },
}

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

Timing guide:

  • 3–5 items: staggerChildren: 0.1
  • 6–12 items: staggerChildren: 0.06
  • 12+ items: staggerChildren: 0.03 (or animate as a group)

Anti-pattern: Don't stagger more than ~15 items individually — it feels slow. Group them or use a wave effect.


Pattern 3: Layout Animations

Automatically animate between layout states. Motion's killer feature.

<motion.div layout transition={{ type: "spring", stiffness: 300, damping: 25 }}>
  {isExpanded ? <ExpandedContent /> : <CollapsedContent />}
</motion.div>

Shared Layout (Tabs, Active Indicators)

{tabs.map((tab) => (
  <button key={tab.id} onClick={() => setActive(tab.id)}>
    {tab.label}
    {active === tab.id && (
      <motion.div
        layoutId="activeTab"
        className="absolute inset-0 bg-primary rounded-md"
        transition={{ type: "spring", stiffness: 400, damping: 30 }}
      />
    )}
  </button>
))}

When to use: Tab indicators, expanding cards, reordering lists, filtering grids.

Performance tip: Add layout="position" if you only need to animate position (not size). It's cheaper.


Pattern 4: Exit Animations (AnimatePresence)

Elements animate out before being removed from the DOM.

<AnimatePresence mode="wait">
  {isVisible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0, scale: 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.95 }}
      transition={{ type: "spring", stiffness: 300, damping: 25 }}
    >
      Modal content
    </motion.div>
  )}
</AnimatePresence>

AnimatePresence modes

ModeBehaviorUse for
"sync" (default)Enter and exit at the same timeCrossfade effects
"wait"Wait for exit to finish before enteringPage transitions, modals
"popLayout"Exiting element pops out of layout flowLists where items are removed

Pattern 5: Hover & Tap Micro-interactions

<motion.button
  whileHover={{ scale: 1.02 }}
  whileTap={{ scale: 0.98 }}
  transition={{ type: "spring", stiffness: 500, damping: 30 }}
>
  Click me
</motion.button>

Hover Patterns by Element Type

ElementwhileHoverwhileTap
Button (primary){ scale: 1.02 }{ scale: 0.98 }
Card{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }—
Icon button{ scale: 1.1 }{ scale: 0.9 }
Link{ x: 2 }—
Avatar{ scale: 1.05 }—

Anti-pattern: Don't scale buttons more than 1.05 — it looks cartoonish. Subtle (1.01–1.03) feels premium.


Pattern 6: Scroll-Triggered Animations

Animate on Scroll Into View

<motion.div
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
  transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
  Appears when scrolled into view
</motion.div>

viewport.once: true — Only animate the first time (most common for landing pages). viewport.margin — Negative margin triggers earlier (before element is fully visible).

Scroll-Linked Progress

const { scrollYProgress } = useScroll()
const opacity = useTransform(scrollYProgress, [0, 0.3], [1, 0])
const y = useTransform(scrollYProgress, [0, 0.3], [0, -50])

<motion.div style={{ opacity, y }}>
  Parallax hero content
</motion.div>

Pattern 7: Page Transitions

// In your layout or page wrapper
<AnimatePresence mode="wait">
  <motion.main
    key={pathname}
    initial={{ opacity: 0, y: 8 }}
    animate={{ opacity: 1, y: 0 }}
    exit={{ opacity: 0, y: -8 }}
    transition={{ type: "spring", stiffness: 300, damping: 30 }}
  >
    {children}
  </motion.main>
</AnimatePresence>

Keep it subtle. Page transitions should be fast (200–300ms feel) and small (8–12px movement). Flashy page transitions feel like 2015.


Pattern 8: Number/Text Transitions

// Animate a counter
<motion.span
  key={count}
  initial={{ opacity: 0, y: -10 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: 10 }}
>
  {count}
</motion.span>

Wrap in AnimatePresence for the exit animation. Great for dashboards, pricing, live data.


Anti-Patterns to Avoid

❌ Don't✅ Do Instead
Animate width/height directlyUse scale or layout animations
Large movement values (y: 200)Subtle values (y: 16–24)
Bounce on everythingReserve bounce for playful/celebratory moments
Animate on every scroll eventUse whileInView with once: true
Different timing for every elementUse consistent spring configs project-wide
Animation on page load for everythingPrioritize above-the-fold; stagger the rest
Custom easing curvesUse springs — they respond to interruption better

Recommended Component Library References

When building animated components, reference these for patterns and inspiration:

  • Magic UI — 150+ animated React components, shadcn/ui compatible
  • Motion Primitives — Copy-paste motion components for React
  • Aceternity UI — Trendy animated components (heavier, more dramatic)

When a user wants a specific animated component (text reveal, animated border, gradient animation, etc.), check these libraries first — there's likely a battle-tested implementation.


Quick Decision Guide

ScenarioPatternSpring Config
Card appearingFade + Risesmooth
List loadingStaggered Listsmooth, 0.08s stagger
Tab switchingShared Layout (layoutId)snappy
Modal open/closeAnimatePresence + scalesmooth
Button presswhileHover + whileTapsnappy
Landing page sectionsScroll-triggeredgentle
Page navigationPage Transitionsmooth
Dashboard counterNumber Transitionsnappy
Notification popupFade + Rise + bouncebouncy
Accordion expandLayout animationsmooth

Examples

Example 1: "Add animations to this card grid"

Apply staggered fade+rise entrance to the grid, hover lift effect on each card:

<motion.div
  variants={container}
  initial="hidden"
  animate="visible"
  className="grid grid-cols-3 gap-4"
>
  {cards.map((card) => (
    <motion.div
      key={card.id}
      variants={item}
      whileHover={{ y: -4, boxShadow: "0 8px 30px rgba(0,0,0,0.12)" }}
      transition={{ type: "spring", stiffness: 300, damping: 25 }}
    >
      <Card {...card} />
    </motion.div>
  ))}
</motion.div>

Example 2: "Make this modal feel better"

Wrap in AnimatePresence, add scale + opacity entrance/exit, overlay fade:

<AnimatePresence>
  {isOpen && (
    <>
      <motion.div
        className="fixed inset-0 bg-black/50"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
      />
      <motion.div
        className="fixed inset-0 flex items-center justify-center"
        initial={{ opacity: 0, scale: 0.95 }}
        animate={{ opacity: 1, scale: 1 }}
        exit={{ opacity: 0, scale: 0.95 }}
        transition={{ type: "spring", stiffness: 300, damping: 25 }}
      >
        <ModalContent onClose={onClose} />
      </motion.div>
    </>
  )}
</AnimatePresence>

Example 3: "Add scroll animations to this landing page"

Apply whileInView with staggered children to each section:

<motion.section
  initial={{ opacity: 0, y: 40 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
  transition={{ type: "spring", stiffness: 200, damping: 20 }}
>
  <h2>Feature Section</h2>
  <motion.div
    variants={container}
    initial="hidden"
    whileInView="visible"
    viewport={{ once: true }}
  >
    {features.map((f) => (
      <motion.div key={f.id} variants={item}>
        <FeatureCard {...f} />
      </motion.div>
    ))}
  </motion.div>
</motion.section>

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.