
Motion Framer
- 2.5k installs
- 629 repo stars
- Updated November 20, 2025
- freshtechbro/claudedesignskills
motion-framer is an agent skill for Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, Animat
About
Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit animations, spring physics, and scroll-based effects. Use when building interactive UI components, micro-interactions, page transitions, or complex animation sequences. The motion-framer skill documents workflows and patterns from the repository SKILL.md. --- name: motion-framer description: Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit animations, spring physics, and scroll-based effects. Use when building interactive UI components, micro-interactions, page transitions, or complex animation sequences. --- # Motion & Framer Motion ## Overview Motion (formerly Framer Motion) is a production-ready animation library for React and JavaScript that enables declarative, performant animations with minimal code. It provides `motion` components that wrap HTML elements with animation superpowers, supports gesture recognition (hover, tap, drag, focus), an.
- Motion & Framer Motion
- Building interactive UI components (buttons, cards, menus)
- Creating micro-interactions and hover effects
- Implementing page transitions and route animations
- Adding scroll-based animations and parallax effects
Motion Framer by the numbers
- 2,458 all-time installs (skills.sh)
- +255 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #148 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
motion-framer capabilities & compatibility
- Capabilities
- motion & framer motion · building interactive ui components (buttons, car · creating micro interactions and hover effects · implementing page transitions and route animatio · adding scroll based animations and parallax effe
- Use cases
- documentation
What motion-framer says it does
--- name: motion-framer description: Modern animation library for React and JavaScript.
Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit animations, spring physics, and scroll-based effects.
Use when building interactive UI components, micro-interactions, page transitions, or complex animation sequences.
Animate Prop The `animate` prop defines the target animation state.
npx skills add https://github.com/freshtechbro/claudedesignskills --skill motion-framerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.5k |
|---|---|
| repo stars | ★ 629 |
| Security audit | 3 / 3 scanners passed |
| Last updated | November 20, 2025 |
| Repository | freshtechbro/claudedesignskills ↗ |
What problem does motion-framer solve for developers using the documented workflows?
Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit anima
Who is it for?
Developers working with motion-framer patterns described in the skill documentation.
Skip if: Skip when docs are empty or the task is outside the skill documented scope.
When should I use this skill?
Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit anima
What you get
Grounded guidance and workflows from SKILL.md for motion-framer.
- Motion component code
- Variant and gesture animation snippets
Files
Motion & Framer Motion
Overview
Motion (formerly Framer Motion) is a production-ready animation library for React and JavaScript that enables declarative, performant animations with minimal code. It provides motion components that wrap HTML elements with animation superpowers, supports gesture recognition (hover, tap, drag, focus), and includes advanced features like layout animations, exit animations, and spring physics.
When to use this skill:
- Building interactive UI components (buttons, cards, menus)
- Creating micro-interactions and hover effects
- Implementing page transitions and route animations
- Adding scroll-based animations and parallax effects
- Animating layout changes (resizing, reordering, shared element transitions)
- Drag-and-drop interfaces
- Complex animation sequences and state-based animations
- Replacing CSS transitions with more powerful, controllable animations
Technology:
- Motion (v11+) - The modern, smaller library from Framer Motion creators
- Framer Motion - The full-featured predecessor (still widely used)
- React 18+ compatible, also supports Vue
- Supports TypeScript
- Works with Next.js, Vite, Remix, and all modern React frameworks
Core Concepts
1. Motion Components
Convert any HTML/SVG element into an animatable component by prefixing with motion.:
import { motion } from "framer-motion"
// Regular HTML becomes motion component
<motion.div />
<motion.button />
<motion.svg />
<motion.path />Every motion component accepts animation props like animate, initial, transition, and gesture props like whileHover, whileTap, etc.
2. Animate Prop
The animate prop defines the target animation state. When values change, Motion automatically animates to them:
// Simple animation - x position changes
<motion.div animate={{ x: 100 }} />
// Multiple properties
<motion.div animate={{ x: 100, opacity: 1, scale: 1.2 }} />
// Animates when state changes
const [isOpen, setIsOpen] = useState(false)
<motion.div animate={{ width: isOpen ? 300 : 100 }} />3. Initial State
Set the initial state before animation using the initial prop:
<motion.div
initial={{ opacity: 0, y: 50 }}
animate={{ opacity: 1, y: 0 }}
/>Set initial={false} to disable initial animations on mount.
4. Transitions
Control how animations move between states using the transition prop:
// Duration-based
<motion.div
animate={{ x: 100 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
// Spring physics
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
/>
// Different transitions for different properties
<motion.div
animate={{ x: 100, opacity: 1 }}
transition={{
x: { type: "spring", stiffness: 300 },
opacity: { duration: 0.2 }
}}
/>Transition types:
"tween"(default) - Duration-based with easing"spring"- Physics-based spring animation"inertia"- Decelerating animation (used in drag)
5. Variants
Organize animation states using named variants for cleaner code and propagation to children:
const variants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
exit: { opacity: 0, scale: 0.9 }
}
<motion.div
variants={variants}
initial="hidden"
animate="visible"
exit="exit"
/>Variant propagation - Children automatically inherit parent variant states:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1 // Stagger child animations
}
}
}
const itemVariants = {
hidden: { x: -20, opacity: 0 },
visible: { x: 0, opacity: 1 }
}
<motion.ul variants={containerVariants} initial="hidden" animate="visible">
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>Common Patterns
1. Hover Animations
Animate on hover using whileHover prop:
// Simple hover effect
<motion.button
whileHover={{ scale: 1.1 }}
transition={{ duration: 0.2 }}
>
Hover me
</motion.button>
// Multiple properties
<motion.div
whileHover={{
scale: 1.05,
backgroundColor: "#f0f0f0",
boxShadow: "0px 10px 30px rgba(0, 0, 0, 0.2)"
}}
>
Hover card
</motion.div>
// With custom transition
<motion.button
whileHover={{
scale: 1.2,
transition: { duration: 0.1 } // Transition for gesture start
}}
transition={{ duration: 0.5 }} // Transition for gesture end
>
Button
</motion.button>Hover with nested elements:
<motion.div whileHover="hover" variants={cardVariants}>
<motion.h3 variants={titleVariants}>Title</motion.h3>
<motion.img variants={imageVariants} />
</motion.div>2. Tap/Press Animations
Animate on tap/press using whileTap prop:
// Scale down on tap
<motion.button
whileTap={{ scale: 0.9 }}
>
Click me
</motion.button>
// Combined hover + tap
<motion.button
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95, rotate: 3 }}
>
Interactive button
</motion.button>
// With variants
const buttonVariants = {
rest: { scale: 1 },
hover: { scale: 1.1 },
pressed: { scale: 0.95 }
}
<motion.button
variants={buttonVariants}
initial="rest"
whileHover="hover"
whileTap="pressed"
>
Button
</motion.button>3. Drag Interactions
Make elements draggable with the drag prop:
// Basic dragging (both axes)
<motion.div drag />
// Constrain to axis
<motion.div drag="x" /> // Only horizontal
<motion.div drag="y" /> // Only vertical
// Drag constraints
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
/>
// Drag with parent constraints
<motion.div ref={constraintsRef}>
<motion.div drag dragConstraints={constraintsRef} />
</motion.div>
// Visual feedback while dragging
<motion.div
drag
whileDrag={{
scale: 1.1,
boxShadow: "0px 10px 20px rgba(0,0,0,0.2)",
cursor: "grabbing"
}}
dragElastic={0.1} // Elasticity when dragging outside constraints
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
/>Drag events:
<motion.div
drag
onDragStart={(event, info) => console.log(info.point)}
onDrag={(event, info) => console.log(info.offset)}
onDragEnd={(event, info) => console.log(info.velocity)}
/>4. Exit Animations (AnimatePresence)
Animate components when they're removed from the DOM using AnimatePresence:
import { AnimatePresence } from "framer-motion"
// Basic exit animation
<AnimatePresence>
{isVisible && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>Key requirements:
- Component must be direct child of
<AnimatePresence> - Must have a unique
keyprop - Use
exitprop to define exit animation
List items with exit animations:
<AnimatePresence>
{items.map(item => (
<motion.li
key={item.id}
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
layout // Smooth layout shifts
>
{item.name}
</motion.li>
))}
</AnimatePresence>Staggered exit animations:
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
when: "beforeChildren",
staggerChildren: 0.1
}
},
exit: {
opacity: 0,
transition: {
when: "afterChildren",
staggerChildren: 0.05,
staggerDirection: -1 // Reverse order
}
}
}
<AnimatePresence>
{show && (
<motion.div variants={containerVariants} initial="hidden" animate="visible" exit="exit">
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
<motion.div variants={itemVariants} />
</motion.div>
)}
</AnimatePresence>5. Layout Animations
Automatically animate layout changes (position, size) with the layout prop:
// Animate all layout changes
<motion.div layout />
// Animate only position changes
<motion.div layout="position" />
// Animate only size changes
<motion.div layout="size" />Grid layout animation:
const [columns, setColumns] = useState(3)
<motion.div className="grid">
{items.map(item => (
<motion.div
key={item.id}
layout
transition={{ layout: { duration: 0.3, ease: "easeInOut" } }}
/>
))}
</motion.div>Shared layout animations (layoutId):
Connect two different elements for smooth transitions using layoutId:
// Tab indicator example
<nav>
{tabs.map(tab => (
<button key={tab.id} onClick={() => setActive(tab.id)}>
{tab.label}
{activeTab === tab.id && (
<motion.div
layoutId="underline"
style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 2 }}
/>
)}
</button>
))}
</nav>
// Modal opening from thumbnail
<motion.img
src={thumbnail}
layoutId="product-image"
onClick={() => setExpanded(true)}
/>
<AnimatePresence>
{expanded && (
<motion.div layoutId="product-image">
<img src={fullsize} />
</motion.div>
)}
</AnimatePresence>6. Scroll-Based Animations
Animate elements when they enter the viewport using whileInView:
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.8 }} // once: trigger once, amount: 80% visible
transition={{ duration: 0.5 }}
>
Animates when scrolled into view
</motion.div>Viewport options:
once: true- Animation triggers only onceamount: 0.5- Percentage of element visible (0-1) or "some" | "all"margin: "-100px"- Offset viewport boundaries
Staggered scroll animations:
<motion.ul
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
variants={{
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 }
},
hidden: { opacity: 0 }
}}
>
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>7. Spring Animations
Use spring physics for natural, bouncy animations:
// Basic spring
<motion.div
animate={{ scale: 1.2 }}
transition={{ type: "spring" }}
/>
// Customize spring physics
<motion.div
animate={{ x: 100 }}
transition={{
type: "spring",
stiffness: 300, // Higher = faster, snappier (default: 100)
damping: 20, // Higher = less bouncy (default: 10)
mass: 1, // Higher = more inertia (default: 1)
}}
/>
// Visual duration (easier spring control)
<motion.div
animate={{ rotate: 90 }}
transition={{
type: "spring",
visualDuration: 0.5, // Perceived duration
bounce: 0.25 // Bounciness (0-1, default: 0.25)
}}
/>Spring presets:
- Gentle:
stiffness: 100, damping: 20 - Wobbly:
stiffness: 200, damping: 10 - Stiff:
stiffness: 400, damping: 30 - Slow:
stiffness: 50, damping: 20
Gesture Recognition
Motion provides declarative gesture handlers:
Gesture Props
<motion.div
whileHover={{ scale: 1.1 }} // Pointer hovers over element
whileTap={{ scale: 0.9 }} // Primary pointer presses element
whileFocus={{ outline: "2px" }} // Element gains focus
whileDrag={{ scale: 1.1 }} // Element is being dragged
whileInView={{ opacity: 1 }} // Element is in viewport
/>Gesture Events
<motion.div
onHoverStart={(event, info) => {}}
onHoverEnd={(event, info) => {}}
onTap={(event, info) => {}}
onTapStart={(event, info) => {}}
onTapCancel={(event, info) => {}}
onDragStart={(event, info) => {}}
onDrag={(event, info) => {}}
onDragEnd={(event, info) => {}}
onViewportEnter={(entry) => {}}
onViewportLeave={(entry) => {}}
/>Event info objects contain:
point: { x, y }- Page coordinatesoffset: { x, y }- Offset from drag startvelocity: { x, y }- Drag velocity
Hooks
useAnimate
Manually control animations with the useAnimate hook:
import { useAnimate } from "framer-motion"
function Component() {
const [scope, animate] = useAnimate()
useEffect(() => {
// Animate multiple elements
animate([
[scope.current, { opacity: 1 }],
["li", { x: 0, opacity: 1 }, { delay: stagger(0.1) }],
[".button", { scale: 1.2 }]
])
}, [])
return (
<div ref={scope}>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button className="button">Click</button>
</div>
)
}Animation controls:
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.speed = 0.5
controls.time = 0 // Seek to startuseSpring
Create spring-animated motion values:
import { useSpring } from "framer-motion"
function Component() {
const x = useSpring(0, { stiffness: 300, damping: 20 })
return (
<motion.div style={{ x }}>
<button onClick={() => x.set(100)}>Move</button>
</motion.div>
)
}useInView
Detect when an element is in viewport:
import { useInView } from "framer-motion"
function Component() {
const ref = useRef(null)
const isInView = useInView(ref, { once: true, amount: 0.5 })
return (
<div ref={ref}>
{isInView ? "In view!" : "Not in view"}
</div>
)
}Integration Patterns
With GSAP
Combine Motion for React state-based animations and GSAP for complex timelines:
import { motion } from "framer-motion"
import gsap from "gsap"
function Component() {
const boxRef = useRef()
const handleClick = () => {
// Use GSAP for complex timeline
const tl = gsap.timeline()
tl.to(boxRef.current, { rotation: 360, duration: 1 })
.to(boxRef.current, { scale: 1.5, duration: 0.5 })
}
return (
// Use Motion for hover/tap/layout animations
<motion.div
ref={boxRef}
whileHover={{ scale: 1.1 }}
onClick={handleClick}
/>
)
}With React Three Fiber
Animate 3D objects using Motion values:
import { motion } from "framer-motion"
import { useFrame } from "@react-three/fiber"
function Box() {
const x = useMotionValue(0)
useFrame(() => {
// Sync Motion value with Three.js position
meshRef.current.position.x = x.get()
})
return (
<>
<mesh ref={meshRef}>
<boxGeometry />
<meshStandardMaterial />
</mesh>
<motion.div
style={{ x }}
drag="x"
dragConstraints={{ left: -5, right: 5 }}
/>
</>
)
}With Form Libraries
Animate form validation states:
import { motion, AnimatePresence } from "framer-motion"
function FormField({ error }) {
return (
<div>
<motion.input
animate={{
borderColor: error ? "#ff0000" : "#cccccc",
x: error ? [0, -10, 10, -10, 10, 0] : 0 // Shake animation
}}
transition={{ duration: 0.4 }}
/>
<AnimatePresence>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
style={{ color: "#ff0000" }}
>
{error}
</motion.p>
)}
</AnimatePresence>
</div>
)
}Performance Optimization
1. Use Transform Properties
Transform properties (x, y, scale, rotate) are hardware-accelerated:
// ✅ Good - Hardware accelerated
<motion.div animate={{ x: 100, scale: 1.2 }} />
// ❌ Avoid - Triggers layout/paint
<motion.div animate={{ left: 100, width: 200 }} />2. Individual Transform Properties
Motion supports individual transform properties for cleaner code:
// Individual properties (Motion feature)
<motion.div style={{ x: 100, rotate: 45, scale: 1.2 }} />
// Traditional (also supported)
<motion.div style={{ transform: "translateX(100px) rotate(45deg) scale(1.2)" }} />3. Reduce Motion for Accessibility
Respect user preferences for reduced motion:
import { useReducedMotion } from "framer-motion"
function Component() {
const shouldReduceMotion = useReducedMotion()
return (
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>
)
}4. Layout Animations Performance
Layout animations can be expensive. Optimize with:
// Specify what to animate
<motion.div layout="position" /> // Only position, not size
// Optimize transition
<motion.div
layout
transition={{
layout: { duration: 0.3, ease: "easeOut" }
}}
/>5. Use layoutId Sparingly
layoutId creates shared layout animations but tracks elements globally. Use only when needed.
Common Pitfalls
1. Forgetting AnimatePresence for Exit Animations
Problem: Exit animations don't work
// ❌ Wrong - No AnimatePresence
{show && <motion.div exit={{ opacity: 0 }} />}// ✅ Correct - Wrapped in AnimatePresence
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>2. Missing key Prop in Lists
Problem: AnimatePresence can't track elements
// ❌ Wrong - No key
<AnimatePresence>
{items.map(item => <motion.div exit={{ opacity: 0 }} />)}
</AnimatePresence>// ✅ Correct - Unique keys
<AnimatePresence>
{items.map(item => (
<motion.div key={item.id} exit={{ opacity: 0 }} />
))}
</AnimatePresence>3. Animating Non-Transform Properties
Problem: Janky animations, poor performance
// ❌ Avoid - Not hardware accelerated
<motion.div animate={{ top: 100, left: 50, width: 200 }} />// ✅ Better - Use transforms
<motion.div animate={{ x: 50, y: 100, scaleX: 2 }} />4. Overusing Layout Animations
Problem: Performance issues with many layout-animated elements
// ❌ Too many layout animations
{items.map(item => <motion.div layout>{item}</motion.div>)}// ✅ Use layout only where needed, optimize others
{items.map(item => (
<motion.div
key={item.id}
animate={{ opacity: 1 }} // Cheaper animation
exit={{ opacity: 0 }}
/>
))}5. Not Using Variants for Complex Animations
Problem: Duplicated animation code, no child orchestration
// ❌ Repetitive
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} />// ✅ Use variants
const variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 }
}
<motion.div variants={variants} initial="hidden" animate="visible" />
<motion.div variants={variants} initial="hidden" animate="visible" />6. Incorrect Transition Timing
Problem: Transitions don't apply to specific gestures
// ❌ Wrong - General transition won't apply to whileHover
<motion.div
whileHover={{ scale: 1.2 }}
transition={{ duration: 1 }} // This applies to animate prop, not whileHover
/>// ✅ Correct - Transition in whileHover or separate gesture transition
<motion.div
whileHover={{
scale: 1.2,
transition: { duration: 0.2 } // Applies to hover start
}}
transition={{ duration: 0.5 }} // Applies to hover end
/>Resources
Official Documentation
- Motion Docs - Official Motion documentation
- Framer Motion Docs - Framer Motion (legacy)
- Motion GitHub - Source code & examples
Bundled Resources
This skill includes:
references/
api_reference.md- Complete Motion API referencevariants_patterns.md- Variant patterns and orchestrationgesture_guide.md- Comprehensive gesture handling guide
scripts/
animation_generator.py- Generate Motion component boilerplatevariant_builder.py- Interactive variant configuration tool
assets/
starter_motion/- Complete Motion + Vite starter templateexamples/- Real-world Motion component patterns
Community Resources
- Motion Dev Discord - Official community
- Framer Motion Examples - Interactive examples
- Motion Recipes - Common patterns
- CodeSandbox Templates - Live demos
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Motion Starter</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body,
#root {
width: 100%;
min-height: 100vh;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
#root {
display: flex;
flex-direction: column;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
{
"name": "motion-starter",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"framer-motion": "^11.15.0"
},
"devDependencies": {
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"vite": "^6.0.11"
}
}
Motion Starter Template
A minimal, production-ready starter template for building animated React applications with Framer Motion.
Features
- ⚡️ Vite - Fast build tool and dev server
- ⚛️ React 18 - Latest React with concurrent features
- 🎨 Framer Motion - Production-ready animation library
- 🎮 Interactive Components - Hover, tap, and drag interactions
- 📤 Exit Animations - AnimatePresence examples
- 🔄 Layout Animations - Smooth layout transitions
- 📜 Stagger Effects - Coordinated child animations
Quick Start
Installation
npm install
# or
yarn
# or
pnpm installDevelopment
npm run devOpens at http://localhost:3000
Build
npm run buildPreview Production Build
npm run previewProject Structure
starter_motion/
├── index.html # Entry HTML
├── package.json # Dependencies
├── vite.config.js # Vite configuration
└── src/
├── main.jsx # React root
├── App.jsx # Main App with examples
├── App.css # Styles
└── components/
├── HoverCard.jsx # Hover animation example
├── DraggableBox.jsx # Drag interaction example
└── StaggerList.jsx # Staggered animation exampleIncluded Examples
1. Hover Animations (HoverCard)
Demonstrates whileHover for interactive cards:
- Scale on hover
- Shadow effects
- Smooth transitions
- Combined with
whileTap
2. Drag Interactions (DraggableBox)
Shows drag functionality:
- Drag constraints
- Visual feedback (
whileDrag) - Drag events (
onDragStart,onDragEnd) - Elastic boundaries
3. Staggered Animations (StaggerList)
Illustrates variant propagation:
- Container/item variant pattern
staggerChildrenfor sequential animationsdelayChildrenfor initial delay- Combined with hover effects
4. Exit Animations (Modal)
Demonstrates AnimatePresence:
- Exit animations with
exitprop - Modal backdrop fade
- Modal enter/exit with spring physics
- Proper cleanup
Common Patterns
Basic Animation
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Content
</motion.div>Hover Effect
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
Click me
</motion.button>Drag Interaction
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
whileDrag={{ scale: 1.1 }}
>
Drag me
</motion.div>Exit Animation
<AnimatePresence>
{show && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>Variants with Stagger
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 }
}
}
const item = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 }
}
<motion.ul variants={container} initial="hidden" animate="visible">
<motion.li variants={item}>Item 1</motion.li>
<motion.li variants={item}>Item 2</motion.li>
</motion.ul>Customization
Add New Components
Create new components in src/components/:
// src/components/MyComponent.jsx
import { motion } from 'framer-motion'
export default function MyComponent() {
return (
<motion.div
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
My Content
</motion.div>
)
}Import in App.jsx:
import MyComponent from './components/MyComponent'
// In App component:
<MyComponent />Change Transition Physics
// Duration-based
<motion.div
animate={{ x: 100 }}
transition={{ duration: 0.5, ease: "easeInOut" }}
/>
// Spring-based (natural, bouncy)
<motion.div
animate={{ x: 100 }}
transition={{ type: "spring", stiffness: 300, damping: 20 }}
/>Add Scroll Animations
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }}
transition={{ duration: 0.5 }}
>
Scroll to reveal
</motion.div>Layout Animations
<motion.div layout>
{/* Content that changes size/position */}
</motion.div>Shared Element Transitions
// Tab indicator
{tabs.map(tab => (
<div key={tab.id}>
{tab.label}
{activeTab === tab.id && (
<motion.div layoutId="underline" />
)}
</div>
))}Performance Tips
1. Use transform properties - Animate x, y, scale, rotate (hardware accelerated) 2. Avoid layout properties - Don't animate width, height, top, left 3. Reduce motion - Respect accessibility preferences:
import { useReducedMotion } from "framer-motion"
const shouldReduceMotion = useReducedMotion()4. Use `will-change` CSS - For complex animations 5. Optimize variants - Reuse variant objects 6. Lazy load heavy components - Use React.lazy() for code splitting
TypeScript Support
To add TypeScript:
npm install -D typescript @types/react @types/react-domRename files to .tsx:
mv src/main.jsx src/main.tsx
mv src/App.jsx src/App.tsxAdd tsconfig.json:
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}Resources
- Framer Motion Docs - Official documentation
- Motion Dev - New Motion library docs
- Examples - Interactive examples
- API Reference - Complete API
Troubleshooting
Animations Not Working
- Ensure component is wrapped in
<motion.*>not regular HTML - Check that
initialandanimateprops are set - Verify transition configuration
Exit Animations Not Working
- Wrap component in
<AnimatePresence> - Ensure component has unique
keyprop - Add
exitprop to motion component
Performance Issues
- Use transform properties (x, y, scale, rotate)
- Avoid animating layout properties
- Reduce number of animated elements
- Use
layout="position"instead oflayout={true}when possible
Drag Not Working
- Check
dragConstraintsare set properly - Ensure parent has defined dimensions
- Verify
dragprop is set to true or "x"/"y"
License
MIT - Use freely for personal and commercial projects.
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
color: white;
}
.header {
text-align: center;
padding: 60px 20px 40px;
}
.header h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 10px;
}
.header p {
font-size: 1.2rem;
opacity: 0.9;
}
.main {
flex: 1;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
width: 100%;
}
.section {
margin-bottom: 80px;
}
.section h2 {
font-size: 2rem;
margin-bottom: 30px;
text-align: center;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.modal-button {
display: block;
margin: 0 auto;
padding: 15px 40px;
font-size: 1.1rem;
font-weight: 600;
color: white;
background: rgba(255, 255, 255, 0.2);
border: 2px solid white;
border-radius: 12px;
cursor: pointer;
backdrop-filter: blur(10px);
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: white;
color: #333;
padding: 40px;
border-radius: 20px;
max-width: 500px;
width: 90%;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modal h3 {
font-size: 1.8rem;
margin-bottom: 15px;
color: #667eea;
}
.modal p {
font-size: 1.1rem;
margin-bottom: 25px;
line-height: 1.6;
}
.close-button {
padding: 12px 30px;
font-size: 1rem;
font-weight: 600;
color: white;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 8px;
cursor: pointer;
}
.footer {
text-align: center;
padding: 40px 20px;
opacity: 0.8;
}
.footer p {
font-size: 0.9rem;
}
import { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import HoverCard from './components/HoverCard'
import DraggableBox from './components/DraggableBox'
import StaggerList from './components/StaggerList'
import './App.css'
function App() {
const [showModal, setShowModal] = useState(false)
return (
<div className="app">
<header className="header">
<motion.h1
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Motion Starter
</motion.h1>
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.2, duration: 0.5 }}
>
Framer Motion animation examples
</motion.p>
</header>
<main className="main">
<section className="section">
<h2>Hover Animations</h2>
<div className="grid">
<HoverCard title="Card 1">
Hover over me to see the animation
</HoverCard>
<HoverCard title="Card 2">
Different hover effects
</HoverCard>
<HoverCard title="Card 3">
Smooth transitions
</HoverCard>
</div>
</section>
<section className="section">
<h2>Drag Interaction</h2>
<DraggableBox />
</section>
<section className="section">
<h2>Staggered List</h2>
<StaggerList />
</section>
<section className="section">
<h2>Exit Animations</h2>
<motion.button
className="modal-button"
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => setShowModal(!showModal)}
>
{showModal ? 'Close' : 'Open'} Modal
</motion.button>
<AnimatePresence mode="wait">
{showModal && (
<motion.div
key="modal"
className="modal-backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setShowModal(false)}
>
<motion.div
className="modal"
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: -20 }}
transition={{ type: 'spring', stiffness: 300, damping: 30 }}
onClick={(e) => e.stopPropagation()}
>
<h3>Modal Title</h3>
<p>This modal animates in and out smoothly with AnimatePresence.</p>
<motion.button
className="close-button"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
onClick={() => setShowModal(false)}
>
Close
</motion.button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</section>
</main>
<footer className="footer">
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ delay: 0.5 }}
>
Built with Framer Motion
</motion.p>
</footer>
</div>
)
}
export default App
import { motion } from 'framer-motion'
import { useState } from 'react'
export default function DraggableBox() {
const [isDragging, setIsDragging] = useState(false)
return (
<div style={{
height: '300px',
position: 'relative',
background: 'rgba(255, 255, 255, 0.05)',
borderRadius: '16px',
border: '2px dashed rgba(255, 255, 255, 0.3)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<motion.div
drag
dragConstraints={{
left: -100,
right: 100,
top: -100,
bottom: 100
}}
dragElastic={0.1}
whileDrag={{
scale: 1.1,
cursor: 'grabbing',
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.4)'
}}
onDragStart={() => setIsDragging(true)}
onDragEnd={() => setIsDragging(false)}
style={{
width: '150px',
height: '150px',
background: isDragging
? 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'
: 'rgba(255, 255, 255, 0.2)',
borderRadius: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'grab',
userSelect: 'none',
fontWeight: 'bold',
fontSize: '1.1rem',
}}
>
{isDragging ? 'Dragging!' : 'Drag me'}
</motion.div>
</div>
)
}
import { motion } from 'framer-motion'
export default function HoverCard({ title, children }) {
return (
<motion.div
className="hover-card"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
whileHover={{
scale: 1.05,
boxShadow: '0 20px 40px rgba(0, 0, 0, 0.3)',
transition: { duration: 0.2 }
}}
whileTap={{ scale: 0.98 }}
style={{
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '16px',
padding: '30px',
cursor: 'pointer',
}}
>
<h3 style={{ fontSize: '1.5rem', marginBottom: '10px' }}>{title}</h3>
<p style={{ opacity: 0.9 }}>{children}</p>
</motion.div>
)
}
import { motion } from 'framer-motion'
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
}
const item = {
hidden: { opacity: 0, x: -20 },
visible: {
opacity: 1,
x: 0,
transition: { duration: 0.5 }
}
}
export default function StaggerList() {
const items = [
{ id: 1, title: 'First Item', description: 'Animates first' },
{ id: 2, title: 'Second Item', description: 'Follows with a delay' },
{ id: 3, title: 'Third Item', description: 'Then this one' },
{ id: 4, title: 'Fourth Item', description: 'And finally this' },
]
return (
<motion.ul
variants={container}
initial="hidden"
animate="visible"
style={{
listStyle: 'none',
maxWidth: '600px',
margin: '0 auto'
}}
>
{items.map((listItem) => (
<motion.li
key={listItem.id}
variants={item}
whileHover={{ scale: 1.02, x: 10 }}
style={{
background: 'rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: '12px',
padding: '20px',
marginBottom: '15px',
cursor: 'pointer',
}}
>
<h4 style={{ fontSize: '1.2rem', marginBottom: '5px' }}>
{listItem.title}
</h4>
<p style={{ opacity: 0.8 }}>{listItem.description}</p>
</motion.li>
))}
</motion.ul>
)
}
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
server: {
host: true,
port: 3000,
},
})
Motion & Framer Motion - Complete API Reference
Comprehensive API documentation for Motion (v11+) and Framer Motion animation libraries.
Table of Contents
1. Motion Component Props 2. Animation Props 3. Gesture Props 4. Layout Props 5. Transition Options 6. Variants 7. Hooks 8. AnimatePresence 9. Utilities
---
Motion Component Props
All motion components (motion.div, motion.button, etc.) accept these props:
Core Animation Props
interface MotionProps {
// Animation state
animate?: AnimationControls | TargetAndTransition | VariantLabels
initial?: boolean | Target | VariantLabels
exit?: TargetAndTransition | VariantLabels
// Transition
transition?: Transition
// Variants
variants?: Variants
// Style
style?: MotionStyle
// Layout
layout?: boolean | "position" | "size"
layoutId?: string
layoutDependency?: any
layoutScroll?: boolean
// Gestures
whileHover?: VariantLabels | TargetAndTransition
whileTap?: VariantLabels | TargetAndTransition
whileFocus?: VariantLabels | TargetAndTransition
whileDrag?: VariantLabels | TargetAndTransition
whileInView?: VariantLabels | TargetAndTransition
// Drag
drag?: boolean | "x" | "y"
dragConstraints?: Constraints | RefObject<Element>
dragElastic?: DragElastic
dragMomentum?: boolean
dragTransition?: InertiaOptions
dragPropagation?: boolean
dragSnapToOrigin?: boolean
// Viewport
viewport?: ViewportOptions
// Events
onUpdate?: (latest: Target) => void
onAnimationStart?: (definition: AnimationDefinition) => void
onAnimationComplete?: (definition: AnimationDefinition) => void
// Hover events
onHoverStart?: (event: MouseEvent, info: EventInfo) => void
onHoverEnd?: (event: MouseEvent, info: EventInfo) => void
// Tap events
onTap?: (event: MouseEvent | TouchEvent | PointerEvent, info: TapInfo) => void
onTapStart?: (event: MouseEvent | TouchEvent | PointerEvent, info: TapInfo) => void
onTapCancel?: (event: MouseEvent | TouchEvent | PointerEvent, info: TapInfo) => void
// Focus events
onFocus?: (event: FocusEvent) => void
onBlur?: (event: FocusEvent) => void
// Drag events
onDragStart?: (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => void
onDrag?: (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => void
onDragEnd?: (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => void
// Viewport events
onViewportEnter?: (entry: IntersectionObserverEntry | null) => void
onViewportLeave?: (entry: IntersectionObserverEntry | null) => void
// Pan events
onPan?: (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => void
onPanStart?: (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => void
onPanEnd?: (event: MouseEvent | TouchEvent | PointerEvent, info: PanInfo) => void
}---
Animation Props
animate
Defines the target animation state. Accepts object, variant label, or animation controls.
// As object
<motion.div animate={{ x: 100, opacity: 1 }} />
// As variant label
<motion.div animate="visible" />
// As array of variant labels (applied in order)
<motion.div animate={["visible", "active"]} />
// Dynamic based on state
<motion.div animate={isOpen ? "open" : "closed"} />Type:
animate?: AnimationControls | TargetAndTransition | VariantLabelsinitial
Sets the initial state before animation. Set to false to disable initial animation.
// As object
<motion.div initial={{ opacity: 0, y: 50 }} />
// As variant label
<motion.div initial="hidden" />
// Disable initial animation
<motion.div initial={false} />Type:
initial?: boolean | Target | VariantLabelsexit
Defines animation when component is removed from DOM. Requires AnimatePresence.
<AnimatePresence>
{show && (
<motion.div
exit={{ opacity: 0, scale: 0.9 }}
/>
)}
</AnimatePresence>Type:
exit?: TargetAndTransition | VariantLabelsstyle
Motion-specific style prop that supports individual transform properties.
<motion.div
style={{
x: 100, // translateX
y: 50, // translateY
scale: 1.2, // scale
rotate: 45, // rotate in degrees
rotateX: 90, // 3D rotation
opacity: 0.5,
backgroundColor: "#ff0000"
}}
/>Transform properties:
x,y,z- Translation (px)scale,scaleX,scaleY- Scale (unitless)rotate,rotateX,rotateY,rotateZ- Rotation (deg)skew,skewX,skewY- Skew (deg)originX,originY,originZ- Transform origin (0-1 or px)perspective- 3D perspective (px)
---
Gesture Props
whileHover
Animation applied while element is hovered.
<motion.button
whileHover={{ scale: 1.1 }}
// Or with custom transition
whileHover={{
scale: 1.2,
transition: { duration: 0.1 }
}}
/>Type:
whileHover?: VariantLabels | TargetAndTransitionwhileTap
Animation applied while element is pressed.
<motion.button
whileTap={{ scale: 0.9, rotate: 3 }}
/>Type:
whileTap?: VariantLabels | TargetAndTransitionwhileFocus
Animation applied while element has focus.
<motion.input
whileFocus={{ borderColor: "#0066ff", scale: 1.02 }}
/>Type:
whileFocus?: VariantLabels | TargetAndTransitionwhileDrag
Animation applied while element is being dragged.
<motion.div
drag
whileDrag={{ scale: 1.1, cursor: "grabbing" }}
/>Type:
whileDrag?: VariantLabels | TargetAndTransitionwhileInView
Animation applied while element is in viewport.
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, amount: 0.5 }}
/>Type:
whileInView?: VariantLabels | TargetAndTransition---
Layout Props
layout
Enables automatic layout animations for position/size changes.
// Animate all layout changes
<motion.div layout />
// Animate only position
<motion.div layout="position" />
// Animate only size
<motion.div layout="size" />Type:
layout?: boolean | "position" | "size"layoutId
Creates shared layout animations between different components.
// Animated tab indicator
{tabs.map(tab => (
<div key={tab.id}>
{tab.label}
{activeTab === tab.id && (
<motion.div layoutId="underline" />
)}
</div>
))}Type:
layoutId?: stringlayoutDependency
Forces layout animation when this value changes.
<motion.div
layout
layoutDependency={sortOrder}
/>Type:
layoutDependency?: any---
Transition Options
Transition Interface
interface Transition {
// Duration-based (tween)
duration?: number
ease?: Easing | Easing[]
times?: number[]
// Spring-based
type?: "tween" | "spring" | "inertia"
stiffness?: number
damping?: number
mass?: number
velocity?: number
restSpeed?: number
restDelta?: number
// Visual spring (easier configuration)
visualDuration?: number
bounce?: number
// Timing
delay?: number
delayChildren?: number
staggerChildren?: number
staggerDirection?: 1 | -1
// Orchestration
when?: "beforeChildren" | "afterChildren" | false
repeat?: number
repeatType?: "loop" | "reverse" | "mirror"
repeatDelay?: number
// Per-property transitions
[key: string]: any
}Tween Transitions (Duration-based)
<motion.div
animate={{ x: 100 }}
transition={{
duration: 0.5,
ease: "easeInOut",
times: [0, 0.5, 1], // Keyframe times
delay: 0.2
}}
/>Easing options:
"linear""easeIn","easeOut","easeInOut""circIn","circOut","circInOut""backIn","backOut","backInOut""anticipate"- Custom array:
[0.42, 0, 0.58, 1](cubic-bezier)
Spring Transitions (Physics-based)
<motion.div
animate={{ x: 100 }}
transition={{
type: "spring",
stiffness: 300, // Higher = faster (default: 100)
damping: 20, // Higher = less bouncy (default: 10)
mass: 1, // Higher = more inertia (default: 1)
velocity: 50 // Initial velocity
}}
/>Visual spring (simplified):
<motion.div
animate={{ rotate: 90 }}
transition={{
type: "spring",
visualDuration: 0.5, // Perceived duration
bounce: 0.25 // Bounciness (0-1)
}}
/>Inertia Transitions (Decelerating)
Used automatically in drag. Can be customized:
<motion.div
drag
dragTransition={{
bounceStiffness: 600,
bounceDamping: 20,
power: 0.3,
timeConstant: 200,
min: 0,
max: 100
}}
/>Orchestration
Staggering children:
const containerVariants = {
visible: {
transition: {
staggerChildren: 0.1, // Delay between each child
delayChildren: 0.2, // Delay before first child
staggerDirection: 1, // 1 = forward, -1 = reverse
when: "beforeChildren" // Animate parent before/after children
}
}
}Per-property transitions:
<motion.div
animate={{ x: 100, opacity: 1 }}
transition={{
x: { type: "spring", stiffness: 300 },
opacity: { duration: 0.2 },
default: { ease: "linear" }
}}
/>Repeating animations:
<motion.div
animate={{ rotate: 360 }}
transition={{
repeat: Infinity,
repeatType: "loop", // "loop" | "reverse" | "mirror"
repeatDelay: 1,
duration: 2
}}
/>---
Variants
Variants are predefined animation states that can be applied to components and their children.
Variant Definition
type Variants = {
[key: string]: TargetAndTransition
}
// Example
const variants: Variants = {
hidden: {
opacity: 0,
y: 20,
transition: { duration: 0.3 }
},
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5, delay: 0.1 }
}
}Variant Propagation
Children inherit parent variant labels automatically:
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1
}
}
}
const item = {
hidden: { x: -20, opacity: 0 },
visible: { x: 0, opacity: 1 }
}
<motion.ul variants={container} initial="hidden" animate="visible">
<motion.li variants={item} />
<motion.li variants={item} />
<motion.li variants={item} />
</motion.ul>Dynamic Variants
Variants can be functions that receive custom data:
const variants = {
visible: (i: number) => ({
opacity: 1,
transition: {
delay: i * 0.1
}
})
}
{items.map((item, i) => (
<motion.div
key={item.id}
custom={i}
variants={variants}
animate="visible"
/>
))}---
Hooks
useAnimate
Manually control animations with imperative API.
import { useAnimate, stagger } from "framer-motion"
function Component() {
const [scope, animate] = useAnimate()
// Animate single element
animate(scope.current, { x: 100 })
// Animate with selector
animate("li", { opacity: 1 })
// Sequence of animations
animate([
[scope.current, { opacity: 1 }],
["li", { x: 0 }, { delay: stagger(0.1) }],
[".button", { scale: 1.2 }]
])
return <div ref={scope}>...</div>
}Returns: [scope: RefObject, animate: AnimateFunction]
AnimationControls methods:
const controls = animate(element, { x: 100 })
controls.play()
controls.pause()
controls.stop()
controls.cancel()
controls.speed = 0.5
controls.time = 0
controls.then(() => console.log("Complete"))useMotionValue
Create a motion value that can be read, set, and animated.
import { useMotionValue } from "framer-motion"
const x = useMotionValue(0)
// Get value
const currentX = x.get()
// Set value
x.set(100)
// Listen to changes
x.on("change", (latest) => console.log(latest))
x.on("animationStart", () => {})
x.on("animationComplete", () => {})
// Use in component
<motion.div style={{ x }} />useTransform
Transform one motion value into another.
import { useMotionValue, useTransform } from "framer-motion"
const x = useMotionValue(0)
// Linear interpolation
const opacity = useTransform(x, [0, 100], [1, 0])
// Custom transform function
const backgroundColor = useTransform(
x,
[0, 100],
["#ff0000", "#0000ff"]
)
<motion.div style={{ x, opacity, backgroundColor }} />useSpring
Create spring-animated motion value.
import { useSpring, useMotionValue } from "framer-motion"
const x = useMotionValue(0)
const springX = useSpring(x, { stiffness: 300, damping: 20 })
<motion.div style={{ x: springX }} />Options:
interface SpringOptions {
stiffness?: number
damping?: number
mass?: number
velocity?: number
restSpeed?: number
restDelta?: number
}useScroll
Track scroll position and velocity.
import { useScroll } from "framer-motion"
const { scrollX, scrollY, scrollXProgress, scrollYProgress } = useScroll()
// With element ref
const ref = useRef(null)
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"]
})
<motion.div style={{ scaleX: scrollYProgress }} />Options:
interface ScrollOptions {
target?: RefObject<Element>
offset?: ["start" | "end" | string, "start" | "end" | string]
container?: RefObject<Element>
layoutEffect?: boolean
}useInView
Detect when element is in viewport.
import { useInView } from "framer-motion"
const ref = useRef(null)
const isInView = useInView(ref, {
once: true,
amount: 0.5,
margin: "-100px"
})
<div ref={ref}>
{isInView ? "Visible!" : "Not visible"}
</div>Options:
interface InViewOptions {
once?: boolean
amount?: "some" | "all" | number
margin?: string
root?: RefObject<Element>
}useReducedMotion
Detect user's motion preferences.
import { useReducedMotion } from "framer-motion"
const shouldReduceMotion = useReducedMotion()
<motion.div
animate={{ x: 100 }}
transition={shouldReduceMotion ? { duration: 0 } : { duration: 0.5 }}
/>useAnimationControls
Create animation controls for imperative animations.
import { useAnimationControls } from "framer-motion"
const controls = useAnimationControls()
controls.start({ x: 100 })
controls.stop()
controls.set({ x: 0 })
<motion.div animate={controls} />usePresence
Detect if component is present (for custom exit animations).
import { usePresence } from "framer-motion"
const [isPresent, safeToRemove] = usePresence()
useEffect(() => {
if (!isPresent) {
// Perform exit animation
animate(ref.current, { opacity: 0 }).then(safeToRemove)
}
}, [isPresent])---
AnimatePresence
Enables exit animations for removed components.
Basic Usage
import { AnimatePresence } from "framer-motion"
<AnimatePresence>
{show && (
<motion.div
key="modal"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>Props
interface AnimatePresenceProps {
// Initial animation on first mount
initial?: boolean
// Custom data for exit animations
custom?: any
// Wait for all exiting animations to complete
mode?: "wait" | "sync" | "popLayout"
// Callback when all exit animations complete
onExitComplete?: () => void
// Propagate exit to nested AnimatePresence
propagate?: boolean
}Mode Options
"sync" (default) - Exit and enter animations happen simultaneously:
<AnimatePresence mode="sync">
<Component key={page} />
</AnimatePresence>"wait" - Wait for exit animation before starting enter animation:
<AnimatePresence mode="wait">
<Component key={page} />
</AnimatePresence>"popLayout" - Exit components render in a separate layer:
<AnimatePresence mode="popLayout">
{items.map(item => (
<motion.div key={item.id} layout />
))}
</AnimatePresence>Custom Data
Pass data to exiting components that can't receive new props:
<AnimatePresence custom={direction}>
<motion.div
key={page}
variants={variants}
custom={direction}
exit="exit"
/>
</AnimatePresence>
const variants = {
exit: (direction) => ({
x: direction > 0 ? 300 : -300
})
}---
Utilities
stagger
Create staggered delays for child animations.
import { stagger } from "framer-motion"
animate("li", { opacity: 1 }, { delay: stagger(0.1) })
// With options
stagger(0.1, {
startDelay: 0.2,
from: "first" | "last" | "center" | number,
ease: "easeInOut"
})animate (standalone)
Animate any element imperatively.
import { animate } from "framer-motion"
// Single element
animate(element, { x: 100 }, { duration: 0.5 })
// Selector
animate(".box", { opacity: 1 })
// Returns controls
const controls = animate(element, { x: 100 })
controls.pause()transform
Transform values without motion values.
import { transform } from "framer-motion"
const output = transform(input, [0, 100], [0, 1])mix
Mix two values.
import { mix } from "framer-motion"
const output = mix(0, 100, 0.5) // 50clamp
Clamp value between min and max.
import { clamp } from "framer-motion"
const output = clamp(0, 100, 150) // 100---
Event Info Types
PanInfo (Drag events)
interface PanInfo {
point: Point // Page coordinates
delta: Point // Change since last event
offset: Point // Offset from gesture start
velocity: Point // Current velocity
}TapInfo
interface TapInfo {
point: Point // Page coordinates
}Point
interface Point {
x: number
y: number
}---
TypeScript Support
Import Types
import type {
TargetAndTransition,
Transition,
Variants,
MotionProps,
AnimationControls,
PanInfo,
TapInfo
} from "framer-motion"Custom Component with Motion
import { motion, HTMLMotionProps } from "framer-motion"
interface Props extends HTMLMotionProps<"div"> {
customProp: string
}
const CustomComponent = ({ customProp, ...props }: Props) => {
return <motion.div {...props}>{customProp}</motion.div>
}---
Performance Tips
1. Use transform properties (x, y, scale, rotate) - hardware accelerated 2. Avoid animating width, height, top, left, margin, padding 3. Use `layout` sparingly - computationally expensive 4. Use `will-change` CSS for complex animations 5. Use `layoutId` only when needed - tracks elements globally 6. Reduce motion values - each creates subscription overhead 7. Use `useReducedMotion` - respect accessibility preferences
---
Common Patterns Quick Reference
// Hover effect
<motion.div whileHover={{ scale: 1.1 }} />
// Tap feedback
<motion.button whileTap={{ scale: 0.95 }} />
// Drag
<motion.div drag dragConstraints={{ left: 0, right: 300 }} />
// Fade in on mount
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} />
// Exit animation
<AnimatePresence>
{show && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
// Layout animation
<motion.div layout />
// Shared layout animation
<motion.div layoutId="shared-element" />
// Scroll-triggered
<motion.div whileInView={{ opacity: 1 }} viewport={{ once: true }} />
// Stagger children
<motion.div variants={container}>
<motion.div variants={item} />
<motion.div variants={item} />
</motion.div>
// Spring animation
<motion.div
animate={{ x: 100 }}
transition={{ type: "spring", stiffness: 300 }}
/>---
For more detailed examples and use cases, see the main SKILL.md and examples in the assets directory.
#!/usr/bin/env python3
"""
Motion/Framer Motion Animation Generator
=========================================
Generate Motion component boilerplate for common animation patterns.
Usage:
python3 animation_generator.py --type hover --name Button --output Button.jsx
python3 animation_generator.py --type exit --name Modal --typescript
python3 animation_generator.py --type drag --name Card --constraints
python3 animation_generator.py --type layout --name Grid --shared-id
Animation Types:
- hover: Hover animation with whileHover
- tap: Tap animation with whileTap
- drag: Draggable component with constraints
- exit: Exit animation with AnimatePresence
- layout: Layout animation with layout prop
- scroll: Scroll-triggered animation with whileInView
- spring: Spring physics animation
- stagger: Staggered children animation
- gesture: Combined gestures (hover + tap + drag)
- variant: Variant-based animation system
- custom: Custom template
Options:
--type: Animation type (required)
--name: Component name (default: Component)
--output: Output file path (default: stdout)
--typescript: Generate TypeScript component
--constraints: Add drag constraints (for drag type)
--shared-id: Add layoutId for shared animations
--spring: Use spring physics for transitions
"""
import argparse
import sys
from typing import Optional, List
class MotionAnimationGenerator:
"""Generate Motion/Framer Motion animation boilerplate."""
def __init__(
self,
animation_type: str,
name: str = "Component",
typescript: bool = False,
constraints: bool = False,
shared_id: Optional[str] = None,
spring: bool = False,
):
self.animation_type = animation_type
self.name = name
self.typescript = typescript
self.constraints = constraints
self.shared_id = shared_id
self.spring = spring
def generate(self) -> str:
"""Generate animation component code."""
generators = {
'hover': self._generate_hover,
'tap': self._generate_tap,
'drag': self._generate_drag,
'exit': self._generate_exit,
'layout': self._generate_layout,
'scroll': self._generate_scroll,
'spring': self._generate_spring,
'stagger': self._generate_stagger,
'gesture': self._generate_gesture,
'variant': self._generate_variant,
'custom': self._generate_custom,
}
generator = generators.get(self.animation_type)
if not generator:
raise ValueError(f"Unknown animation type: {self.animation_type}")
return generator()
def _get_imports(self, needs_presence: bool = False, needs_hooks: bool = False) -> str:
"""Generate import statements."""
imports = ["import { motion"]
if needs_presence:
imports[0] += ", AnimatePresence"
imports[0] += " } from 'framer-motion'"
if needs_hooks:
imports.append("import { useState } from 'react'")
if self.typescript:
imports.append("import type { Variants } from 'framer-motion'")
return "\n".join(imports)
def _get_props_interface(self) -> str:
"""Generate TypeScript props interface."""
if not self.typescript:
return ""
return f"""
interface {self.name}Props {{
children?: React.ReactNode
}}
"""
def _generate_hover(self) -> str:
"""Generate hover animation component."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = f"({{ children }}: {self.name}Props)" if self.typescript else "({ children })"
transition = "{ type: 'spring', stiffness: 300, damping: 20 }" if self.spring else "{ duration: 0.2 }"
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
return (
<motion.div
whileHover={{{{
scale: 1.05,
transition: {transition}
}}}}
transition={{{{ duration: 0.3 }}}}
>
{{children}}
</motion.div>
)
}}
"""
def _generate_tap(self) -> str:
"""Generate tap animation component."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = f"({{ children }}: {self.name}Props)" if self.typescript else "({ children })"
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
return (
<motion.button
whileHover={{{{ scale: 1.05 }}}}
whileTap={{{{ scale: 0.95, rotate: 2 }}}}
transition={{{{ type: 'spring', stiffness: 400, damping: 17 }}}}
>
{{children}}
</motion.button>
)
}}
"""
def _generate_drag(self) -> str:
"""Generate drag animation component."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = f"({{ children }}: {self.name}Props)" if self.typescript else "({ children })"
constraints_code = ""
if self.constraints:
constraints_code = """
dragConstraints={{{{ left: -100, right: 100, top: -100, bottom: 100 }}}}
dragElastic={{0.1}}"""
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
return (
<motion.div
drag{constraints_code}
whileDrag={{{{
scale: 1.1,
cursor: 'grabbing',
boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.3)'
}}}}
dragTransition={{{{ bounceStiffness: 600, bounceDamping: 20 }}}}
>
{{children}}
</motion.div>
)
}}
"""
def _generate_exit(self) -> str:
"""Generate exit animation with AnimatePresence."""
imports = self._get_imports(needs_presence=True, needs_hooks=True)
props_interface = self._get_props_interface()
return f"""{imports}
{props_interface}
export function {self.name}({{ children }}{': ' + self.name + 'Props' if self.typescript else ''}) {{
const [isVisible, setIsVisible] = useState(true)
return (
<>
<button onClick={{() => setIsVisible(!isVisible)}}>
Toggle
</button>
<AnimatePresence mode="wait">
{{isVisible && (
<motion.div
key="content"
initial={{{{ opacity: 0, y: 20 }}}}
animate={{{{ opacity: 1, y: 0 }}}}
exit={{{{ opacity: 0, y: -20 }}}}
transition={{{{ duration: 0.3 }}}}
>
{{children}}
</motion.div>
)}}
</AnimatePresence>
</>
)
}}
"""
def _generate_layout(self) -> str:
"""Generate layout animation component."""
imports = self._get_imports(needs_hooks=True)
props_interface = self._get_props_interface()
layout_id = f'layoutId="{self.shared_id}"' if self.shared_id else ""
return f"""{imports}
{props_interface}
export function {self.name}({{ children }}{': ' + self.name + 'Props' if self.typescript else ''}) {{
const [isExpanded, setIsExpanded] = useState(false)
return (
<motion.div
layout
{layout_id}
onClick={{() => setIsExpanded(!isExpanded)}}
style={{{{
width: isExpanded ? '400px' : '200px',
height: isExpanded ? '300px' : '150px',
borderRadius: '12px',
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
cursor: 'pointer',
padding: '20px'
}}}}
transition={{{{
layout: {{{{ duration: 0.3, ease: 'easeInOut' }}}}
}}}}
>
<motion.div layout="position">
{{children}}
</motion.div>
</motion.div>
)
}}
"""
def _generate_scroll(self) -> str:
"""Generate scroll-triggered animation."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = f"({{ children }}: {self.name}Props)" if self.typescript else "({ children })"
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
return (
<motion.div
initial={{{{ opacity: 0, y: 50 }}}}
whileInView={{{{ opacity: 1, y: 0 }}}}
viewport={{{{ once: true, amount: 0.3 }}}}
transition={{{{ duration: 0.5, ease: 'easeOut' }}}}
>
{{children}}
</motion.div>
)
}}
"""
def _generate_spring(self) -> str:
"""Generate spring physics animation."""
imports = self._get_imports(needs_hooks=True)
props_interface = self._get_props_interface()
return f"""{imports}
{props_interface}
export function {self.name}({{ children }}{': ' + self.name + 'Props' if self.typescript else ''}) {{
const [isActive, setIsActive] = useState(false)
return (
<motion.div
animate={{{{
scale: isActive ? 1.2 : 1,
rotate: isActive ? 5 : 0
}}}}
transition={{{{
type: 'spring',
stiffness: 300,
damping: 20,
mass: 1
}}}}
onClick={{() => setIsActive(!isActive)}}
>
{{children}}
</motion.div>
)
}}
"""
def _generate_stagger(self) -> str:
"""Generate staggered children animation."""
imports = self._get_imports()
if self.typescript:
imports = self._get_imports() + "\nimport type { Variants } from 'framer-motion'"
variants = "Variants" if self.typescript else ""
return f"""{imports}
const container{': ' + variants if variants else ''} = {{
hidden: {{{{ opacity: 0 }}}},
visible: {{{{
opacity: 1,
transition: {{{{
staggerChildren: 0.1,
delayChildren: 0.2
}}}}
}}}}
}}
const item{': ' + variants if variants else ''} = {{
hidden: {{{{ opacity: 0, y: 20 }}}},
visible: {{{{
opacity: 1,
y: 0,
transition: {{{{ duration: 0.5 }}}}
}}}}
}}
export function {self.name}() {{
const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5']
return (
<motion.ul
variants={{container}}
initial="hidden"
animate="visible"
style={{{{ listStyle: 'none', padding: 0 }}}}
>
{{items.map((text, index) => (
<motion.li
key={{index}}
variants={{item}}
style={{{{
padding: '20px',
marginBottom: '10px',
background: '#f0f0f0',
borderRadius: '8px'
}}}}
>
{{text}}
</motion.li>
))}}
</motion.ul>
)
}}
"""
def _generate_gesture(self) -> str:
"""Generate combined gesture component."""
imports = self._get_imports(needs_hooks=True)
props_interface = self._get_props_interface()
return f"""{imports}
{props_interface}
export function {self.name}({{ children }}{': ' + self.name + 'Props' if self.typescript else ''}) {{
const [isDragging, setIsDragging] = useState(false)
return (
<motion.div
drag
dragConstraints={{{{ left: 0, right: 300, top: 0, bottom: 300 }}}}
whileHover={{{{ scale: 1.05 }}}}
whileTap={{{{ scale: 0.95 }}}}
whileDrag={{{{ scale: 1.1, cursor: 'grabbing' }}}}
onDragStart={{() => setIsDragging(true)}}
onDragEnd={{() => setIsDragging(false)}}
style={{{{
width: '150px',
height: '150px',
background: isDragging ? '#667eea' : '#764ba2',
borderRadius: '12px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'grab',
userSelect: 'none'
}}}}
>
{{children}}
</motion.div>
)
}}
"""
def _generate_variant(self) -> str:
"""Generate variant-based animation system."""
imports = self._get_imports(needs_hooks=True)
if self.typescript:
imports += "\nimport type { Variants } from 'framer-motion'"
variants_type = ": Variants" if self.typescript else ""
return f"""{imports}
const variants{variants_type} = {{
inactive: {{
scale: 1,
backgroundColor: '#cccccc',
transition: {{{{ duration: 0.3 }}}}
}},
active: {{
scale: 1.1,
backgroundColor: '#667eea',
transition: {{{{ type: 'spring', stiffness: 300, damping: 20 }}}}
}},
complete: {{
scale: 1,
backgroundColor: '#10b981',
transition: {{{{ duration: 0.3 }}}}
}}
}}
export function {self.name}() {{
const [status, setStatus] = useState<'inactive' | 'active' | 'complete'>('inactive')
const handleClick = () => {{
if (status === 'inactive') {{
setStatus('active')
setTimeout(() => setStatus('complete'), 1000)
}} else {{
setStatus('inactive')
}}
}}
return (
<motion.div
variants={{variants}}
animate={{status}}
onClick={{handleClick}}
style={{{{
width: '200px',
height: '60px',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
color: 'white',
fontWeight: 'bold'
}}}}
>
{{status.toUpperCase()}}
</motion.div>
)
}}
"""
def _generate_custom(self) -> str:
"""Generate custom template."""
imports = self._get_imports()
props_interface = self._get_props_interface()
props_sig = f"({{ children }}: {self.name}Props)" if self.typescript else "({ children })"
return f"""{imports}
{props_interface}
export function {self.name}{props_sig} {{
return (
<motion.div
initial={{{{ opacity: 0 }}}}
animate={{{{ opacity: 1 }}}}
transition={{{{ duration: 0.5 }}}}
>
{{children}}
</motion.div>
)
}}
"""
def main():
parser = argparse.ArgumentParser(
description='Generate Motion/Framer Motion animation boilerplate',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--type',
required=True,
choices=['hover', 'tap', 'drag', 'exit', 'layout', 'scroll',
'spring', 'stagger', 'gesture', 'variant', 'custom'],
help='Animation type to generate'
)
parser.add_argument(
'--name',
default='Component',
help='Component name (default: Component)'
)
parser.add_argument(
'--output',
help='Output file path (default: stdout)'
)
parser.add_argument(
'--typescript',
action='store_true',
help='Generate TypeScript component'
)
parser.add_argument(
'--constraints',
action='store_true',
help='Add drag constraints (for drag type)'
)
parser.add_argument(
'--shared-id',
help='Add layoutId for shared animations'
)
parser.add_argument(
'--spring',
action='store_true',
help='Use spring physics for transitions'
)
args = parser.parse_args()
# Generate animation
generator = MotionAnimationGenerator(
animation_type=args.type,
name=args.name,
typescript=args.typescript,
constraints=args.constraints,
shared_id=args.shared_id,
spring=args.spring,
)
try:
code = generator.generate()
# Output
if args.output:
with open(args.output, 'w') as f:
f.write(code)
print(f"✅ Generated {args.name} component → {args.output}")
else:
print(code)
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Motion Variant Builder
======================
Interactive CLI tool to build Motion/Framer Motion variant configurations.
Usage:
python3 variant_builder.py
python3 variant_builder.py --preset fade --output variants.js
python3 variant_builder.py --typescript
Presets:
- fade: Fade in/out animation
- slide: Slide animation (left, right, up, down)
- scale: Scale animation
- rotate: Rotation animation
- stagger: Staggered children animation
- modal: Modal enter/exit animation
- page: Page transition animation
- custom: Build from scratch
Features:
- Interactive CLI for building variants
- Multiple animation states
- Transition configurations
- Stagger settings for children
- TypeScript support
- Code generation
"""
import argparse
import sys
import json
from typing import Dict, List, Optional, Any
class VariantBuilder:
"""Build Motion variant configurations interactively."""
PRESETS = {
'fade': {
'hidden': {
'opacity': 0
},
'visible': {
'opacity': 1,
'transition': {'duration': 0.5}
}
},
'slide': {
'hidden': {
'opacity': 0,
'x': -100
},
'visible': {
'opacity': 1,
'x': 0,
'transition': {'type': 'spring', 'stiffness': 300, 'damping': 30}
}
},
'scale': {
'hidden': {
'opacity': 0,
'scale': 0.8
},
'visible': {
'opacity': 1,
'scale': 1,
'transition': {'duration': 0.3}
}
},
'rotate': {
'hidden': {
'opacity': 0,
'rotate': -180
},
'visible': {
'opacity': 1,
'rotate': 0,
'transition': {'type': 'spring', 'stiffness': 200, 'damping': 20}
}
},
'stagger': {
'hidden': {
'opacity': 0
},
'visible': {
'opacity': 1,
'transition': {
'staggerChildren': 0.1,
'delayChildren': 0.2
}
}
},
'modal': {
'hidden': {
'opacity': 0,
'scale': 0.9,
'y': 20
},
'visible': {
'opacity': 1,
'scale': 1,
'y': 0,
'transition': {
'type': 'spring',
'stiffness': 300,
'damping': 30
}
},
'exit': {
'opacity': 0,
'scale': 0.9,
'y': -20,
'transition': {'duration': 0.2}
}
},
'page': {
'initial': {
'opacity': 0,
'x': 300
},
'in': {
'opacity': 1,
'x': 0,
'transition': {'duration': 0.3}
},
'out': {
'opacity': 0,
'x': -300,
'transition': {'duration': 0.3}
}
}
}
def __init__(self, preset: Optional[str] = None, typescript: bool = False):
self.variants: Dict[str, Dict[str, Any]] = {}
self.typescript = typescript
if preset and preset in self.PRESETS:
self.variants = self.PRESETS[preset].copy()
def add_state(self, name: str, properties: Dict[str, Any]) -> None:
"""Add a variant state."""
self.variants[name] = properties
def generate_code(self) -> str:
"""Generate variant code."""
if self.typescript:
return self._generate_typescript()
else:
return self._generate_javascript()
def _generate_javascript(self) -> str:
"""Generate JavaScript variant code."""
code = "const variants = {\n"
for state_name, properties in self.variants.items():
code += f" {state_name}: {{\n"
code += self._format_properties(properties, indent=4)
code += " },\n"
code += "}\n\nexport default variants"
return code
def _generate_typescript(self) -> str:
"""Generate TypeScript variant code."""
code = "import type { Variants } from 'framer-motion'\n\n"
code += "const variants: Variants = {\n"
for state_name, properties in self.variants.items():
code += f" {state_name}: {{\n"
code += self._format_properties(properties, indent=4)
code += " },\n"
code += "}\n\nexport default variants"
return code
def _format_properties(self, properties: Dict[str, Any], indent: int = 0) -> str:
"""Format properties as JavaScript object."""
lines = []
indent_str = " " * indent
for key, value in properties.items():
if isinstance(value, dict):
lines.append(f"{indent_str}{key}: {{")
lines.append(self._format_properties(value, indent + 2).rstrip())
lines.append(f"{indent_str}}},")
elif isinstance(value, str):
lines.append(f"{indent_str}{key}: '{value}',")
elif isinstance(value, bool):
lines.append(f"{indent_str}{key}: {str(value).lower()},")
else:
lines.append(f"{indent_str}{key}: {value},")
return "\n".join(lines) + "\n"
def interactive_build(self) -> None:
"""Interactive CLI for building variants."""
print("\n🎨 Motion Variant Builder - Interactive Mode\n")
print("=" * 60)
# Choose preset or custom
print("\nChoose a starting point:")
print(" 1. fade - Simple fade in/out")
print(" 2. slide - Slide animation")
print(" 3. scale - Scale animation")
print(" 4. rotate - Rotation animation")
print(" 5. stagger - Staggered children")
print(" 6. modal - Modal enter/exit")
print(" 7. page - Page transition")
print(" 8. custom - Build from scratch")
choice = input("\nSelect preset (1-8): ").strip()
preset_map = {
'1': 'fade',
'2': 'slide',
'3': 'scale',
'4': 'rotate',
'5': 'stagger',
'6': 'modal',
'7': 'page',
}
if choice in preset_map:
preset = preset_map[choice]
self.variants = self.PRESETS[preset].copy()
print(f"\n✅ Loaded '{preset}' preset")
else:
print("\n📝 Building custom variants...")
# Add/modify states
while True:
print("\n" + "=" * 60)
print("Current variants:")
for state_name in self.variants.keys():
print(f" - {state_name}")
print("\nOptions:")
print(" 1. Add new state")
print(" 2. Modify existing state")
print(" 3. Remove state")
print(" 4. Preview code")
print(" 5. Done")
option = input("\nChoose option (1-5): ").strip()
if option == '1':
self._add_state_interactive()
elif option == '2':
self._modify_state_interactive()
elif option == '3':
self._remove_state_interactive()
elif option == '4':
print("\n" + "=" * 60)
print("Generated Code:")
print("=" * 60)
print(self.generate_code())
elif option == '5':
break
def _add_state_interactive(self) -> None:
"""Add state interactively."""
print("\n📝 Add New State")
state_name = input("State name (e.g., 'hidden', 'visible', 'exit'): ").strip()
if not state_name:
print("❌ Invalid state name")
return
properties = self._build_properties_interactive()
self.variants[state_name] = properties
print(f"✅ Added state '{state_name}'")
def _modify_state_interactive(self) -> None:
"""Modify state interactively."""
if not self.variants:
print("❌ No states to modify")
return
print("\n✏️ Modify State")
print("Available states:")
for i, name in enumerate(self.variants.keys(), 1):
print(f" {i}. {name}")
choice = input("\nSelect state number: ").strip()
try:
state_name = list(self.variants.keys())[int(choice) - 1]
print(f"\nModifying '{state_name}'...")
properties = self._build_properties_interactive()
self.variants[state_name] = properties
print(f"✅ Modified state '{state_name}'")
except (ValueError, IndexError):
print("❌ Invalid selection")
def _remove_state_interactive(self) -> None:
"""Remove state interactively."""
if not self.variants:
print("❌ No states to remove")
return
print("\n🗑️ Remove State")
print("Available states:")
for i, name in enumerate(self.variants.keys(), 1):
print(f" {i}. {name}")
choice = input("\nSelect state number: ").strip()
try:
state_name = list(self.variants.keys())[int(choice) - 1]
del self.variants[state_name]
print(f"✅ Removed state '{state_name}'")
except (ValueError, IndexError):
print("❌ Invalid selection")
def _build_properties_interactive(self) -> Dict[str, Any]:
"""Build properties interactively."""
properties = {}
print("\nAdd properties (leave empty to skip):")
# Common animation properties
opacity = input(" opacity (0-1): ").strip()
if opacity:
properties['opacity'] = float(opacity)
x = input(" x position (px): ").strip()
if x:
properties['x'] = int(x)
y = input(" y position (px): ").strip()
if y:
properties['y'] = int(y)
scale = input(" scale (0-n): ").strip()
if scale:
properties['scale'] = float(scale)
rotate = input(" rotate (degrees): ").strip()
if rotate:
properties['rotate'] = int(rotate)
# Transition
add_transition = input("\nAdd transition? (y/n): ").strip().lower()
if add_transition == 'y':
transition = self._build_transition_interactive()
if transition:
properties['transition'] = transition
return properties
def _build_transition_interactive(self) -> Dict[str, Any]:
"""Build transition interactively."""
transition = {}
print("\nTransition type:")
print(" 1. tween (duration-based)")
print(" 2. spring (physics-based)")
print(" 3. stagger (for children)")
choice = input("Select type (1-3): ").strip()
if choice == '1':
duration = input(" duration (seconds): ").strip()
if duration:
transition['duration'] = float(duration)
ease = input(" ease (linear/easeIn/easeOut/easeInOut): ").strip()
if ease:
transition['ease'] = ease
elif choice == '2':
transition['type'] = 'spring'
stiffness = input(" stiffness (default 100): ").strip()
if stiffness:
transition['stiffness'] = int(stiffness)
damping = input(" damping (default 10): ").strip()
if damping:
transition['damping'] = int(damping)
elif choice == '3':
stagger = input(" staggerChildren (seconds): ").strip()
if stagger:
transition['staggerChildren'] = float(stagger)
delay = input(" delayChildren (seconds): ").strip()
if delay:
transition['delayChildren'] = float(delay)
return transition
def main():
parser = argparse.ArgumentParser(
description='Build Motion variant configurations',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
parser.add_argument(
'--preset',
choices=['fade', 'slide', 'scale', 'rotate', 'stagger', 'modal', 'page'],
help='Use a preset variant configuration'
)
parser.add_argument(
'--output',
help='Output file path (default: stdout)'
)
parser.add_argument(
'--typescript',
action='store_true',
help='Generate TypeScript code'
)
parser.add_argument(
'--interactive',
action='store_true',
help='Run in interactive mode'
)
args = parser.parse_args()
# Create builder
builder = VariantBuilder(preset=args.preset, typescript=args.typescript)
# Run interactive mode if requested or no preset given
if args.interactive or (not args.preset and len(sys.argv) == 1):
builder.interactive_build()
# Generate code
if builder.variants:
code = builder.generate_code()
# Output
if args.output:
with open(args.output, 'w') as f:
f.write(code)
print(f"\n✅ Variants generated → {args.output}")
else:
print("\n" + "=" * 60)
print("Generated Code:")
print("=" * 60)
print(code)
else:
print("❌ No variants to generate")
sys.exit(1)
if __name__ == '__main__':
main()
Related skills
How it compares
Use motion-framer for declarative React Motion patterns; use awwwards-animations when the brief requires GSAP scroll choreography across many libraries.
FAQ
Who is Motion Framer for?
Developers and software engineers working with motion-framer patterns from the skill documentation.
When should I use Motion Framer?
Modern animation library for React and JavaScript. Create smooth, production-ready animations with motion components, variants, gestures (hover/tap/drag), layout animations, AnimatePresence exit animations, spring physic
Is Motion Framer safe to install?
Review the Security Audits panel on this page before installing in production.