framer-motion · athevon — Claude Code skill: what it does, how to install
Guides coding agents on Framer Motion and Motion patterns in React: layout transitions, gestures, and DOM-direct motion values.
npx skills add AThevon/genjutsu --skill framer-motion -a claude-codeWhat framer-motion does
The skill gives AI assistants strict rules for working with Framer Motion and Motion across React 18 and 19 projects. It enforces package boundary checks: the agent must inspect package.json and keep whichever library name is installed (motion via motion/react or legacy framer-motion) rather than forcing an unprompted migration.
The guidelines detail exit handling with AnimatePresence, shared layout animations via layoutId, and gesture bindings like dragConstraints or dragElastic. It flags recent deprecations, such as replacing staggerChildren with the newer stagger() helper, while reminding the model that motion values update DOM styles directly without triggering component re-renders.
If you pair Motion 13+ with styled-components or Emotion, you must explicitly inject isValidProp via MotionConfig to avoid passing internal animation props to DOM nodes.
What it helps with
- implement exit animations using
AnimatePresencewith proper keys - build shared layout transitions across components with
layoutId - configure drag gestures with boundary constraints and snap-back behavior
- create scroll-linked transformations with
useScrollanduseTransform - prevent infinite re-render loops caused by state updates inside motion callbacks
Example prompts for the agent
- Create a modal dialog with enter and exit animations using AnimatePresence in mode wait
- Add an animated tab indicator that slides between items using layoutId
- Set up a draggable card component with dragConstraints and spring reset on release
How to install framer-motion
1Skills CLI (Vercel)
npx skills add AThevon/genjutsu --skill framer-motion -a claude-code2Manually: put the files in place
~/.claude/skills/framer-motion/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/framer-motion/
1Skills CLI (Vercel)
npx skills add AThevon/genjutsu --skill framer-motion -a codex2Manually: put the files in place
.agents/skills/framer-motion/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add AThevon/genjutsu --skill framer-motion -a cursor2Manually: put the files in place
.cursor/skills/framer-motion/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install AThevon/genjutsu framer-motion2Manually: put the files in place
.github/skills/framer-motion/SKILL.md1Skills CLI (Vercel)
npx skills add AThevon/genjutsu --skill framer-motion -a gemini-cli2Platform CLI
gemini skills install https://github.com/AThevon/genjutsu --path skills/_jutsu/framer-motionManually: put the files in place
.devin/skills/framer-motion/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add AThevon/genjutsu --skill framer-motion -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about framer-motion
How to install framer-motion in Claude Code?
Run npx skills add AThevon/genjutsu --skill framer-motion -a claude-code. You can also place the skill file directly into ~/.claude/skills/framer-motion/SKILL.md.
What does the framer-motion skill do?
It instructs AI assistants on correct Framer Motion and Motion 13 practices, including exit transitions, gestures, scroll hooks, and avoiding React re-renders.
How does the skill handle the motion versus framer-motion package change?
It forbids automated migrations without user consent. The assistant inspects package.json and uses imports matching the existing dependency, using motion/react for motion and direct imports for framer-motion.
Source text
SKILL.md · MIT
---
name: framer-motion
description: "Framer Motion / Motion sub-skill - AnimatePresence, layout animations, gestures, motion values."
metadata:
internal: true
---
> **Version-sensitive.** Every API name, SDK gate and browser-support claim below was
> verified on **2026-09-08** against primary sources. What against, and when, is in
> `_jutsu/VERSIONS.md`. If that date is old, re-verify before acting on a version number.
# Framer Motion - Sub-skill
> **Two package names, one library.** Framer Motion was renamed to Motion. `motion` and
> `framer-motion` both publish the same version (13.2.0 as of 2026-09-08): `motion` declares
> `"framer-motion": "^13.2.0"` as a dependency and `motion/react` re-exports it. Neither is
> broken, and `framer-motion` is still the one most installed projects have.
>
> **Read `package.json` and follow what is there.** Do not migrate a project from one to the
> other unless the user asks - that is Iron Rule 8 in `cast` / 10 in `paint`.
>
> | In `package.json` | Import from | Install line |
> |---|---|---|
> | `motion` | `"motion/react"` | `npm install motion` |
> | `framer-motion` | `"framer-motion"` | `npm install framer-motion` |
> | both | whichever the file you are editing already imports; say the project is mid-migration | - |
>
> Every API in this skill is identical across the two names at v13. Where a symbol is newer
> than v11, it is flagged inline. Peers: `react` / `react-dom` `^18 || ^19`.
## When to use Framer Motion vs alternatives
| Criteria | Framer Motion | GSAP | Native CSS |
|---------|--------------|------|-----------|
| Layout animations | Excellent (layoutId) | Manual | Impossible |
| Exit animations | AnimatePresence | Timeline reverse | Limited (display) |
| Gestures (drag, hover) | Native, declarative | Draggable plugin | Basic |
| Scroll-driven | useScroll + useTransform | ScrollTrigger (more powerful) | scroll-timeline |
| Complex orchestration | Variants + propagation | Timeline (more flexible) | @keyframes |
| Bundle size | ~50kb tree-shaken | ~30kb core | 0kb |
| React integration | Native, component-first | Refs + useGSAP | className toggle |
**Rule**: Framer Motion for React UI interactions (modals, toasts, reorder, shared layout). GSAP for complex timelines, cinematic scroll-driven, SVG morphing.
## AnimatePresence - Exit animations
```tsx
<AnimatePresence mode="wait">
{isVisible && (
<motion.div
key="unique-key" // REQUIRED - identifies the component
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
```
- `mode="wait"` - waits for exit to finish before enter (page transitions)
- `mode="sync"` - exit and enter simultaneously
- `mode="popLayout"` - removes from flow immediately (good for lists)
- `onExitComplete` - callback when all exit animations are finished
## Layout animations
```tsx
// Shared layout - the element "slides" between two positions
<motion.div layoutId="highlight" className={activeTab === id ? "active" : ""} />
// Auto layout - animates position/size when layout changes
<motion.div layout>
{isExpanded && <motion.p layout>Additional content</motion.p>}
</motion.div>
// layout="position" - animates position only (not size)
// layout="size" - animates size only
// layout="preserve-aspect" - preserves the ratio during the transition
```
## Variants - Propagation and orchestration
```tsx
import { stagger } from "motion/react";
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
// staggerChildren + staggerDirection are DEPRECATED (Motion 12.22, Jul 2025).
// Pass a stagger() function to delayChildren instead.
delayChildren: stagger(0.08, { startDelay: 0.2 }),
// reverse order: stagger(0.08, { from: "last" })
// also available: from: "first" | "center" | "last" | index, and ease
},
},
};
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
};
<motion.ul variants={container} initial="hidden" animate="show">
{items.map((i) => (
<motion.li key={i.id} variants={item} />
))}
</motion.ul>
```
Variants **automatically propagate** to motion children - no need for `initial`/`animate` on children.
## Gestures
```tsx
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
whileFocus={{ borderColor: "#3b82f6" }}
// Drag
drag // true = x+y, "x" = horizontal only, "y" = vertical only
dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
dragElastic={0.2} // 0 = rigid, 1 = free (default 0.35)
dragSnapToOrigin // returns to initial position
onDragEnd={(e, info) => {
if (info.offset.x > 100) handleSwipe("right");
}}
/>
```
## Motion values - Reactive without re-render
```tsx
const x = useMotionValue(0);
const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0]);
const background = useTransform(x, [-200, 200], ["#ff0000", "#00ff00"]);
// Spring-based smoothing
const smoothX = useSpring(x, { stiffness: 300, damping: 30 });
// Scroll tracking
const { scrollY, scrollYProgress } = useScroll();
const parallaxY = useTransform(scrollYProgress, [0, 1], [0, -300]);
// Element-scoped scroll
const ref = useRef(null);
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"],
});
```
**Motion values do NOT trigger React re-renders** - they update the DOM directly via `style`.
## Do Not
### Do not use motion with styled-components / Emotion without `isValidProp` (v13+)
Motion 13.0 removed `@emotion/is-prop-valid` as an optional dependency. Without explicit injection, motion-only props leak onto the DOM.
```tsx
import isPropValid from "@emotion/is-prop-valid";
import { MotionConfig } from "motion/react";
<MotionConfig isValidProp={isPropValid}>
<App />
</MotionConfig>
```
Or reverse the composition so the styling library owns prop forwarding: `const MotionDiv = motion.create(StyledDiv)`.
### Do not setState in callbacks without a guard
```tsx
// BAD - infinite re-render if animate depends on state
onUpdate={(latest) => setPosition(latest.x)}
// GOOD - guard or useMotionValueEvent
const x = useMotionValue(0);
useMotionValueEvent(x, "change", (latest) => {
if (latest > threshold) onThresholdReached();
});
```
### Do not use layout animation without a stable key
```tsx
// BAD - key changes every render, breaks layout tracking
<motion.div layout key={Math.random()} />
// GOOD - stable key derived from data
<motion.div layout key={item.id} />
```
### Do not forget the unique key on AnimatePresence
```tsx
// BAD - no key, exit animation does not trigger
<AnimatePresence>
{isOpen && <motion.div exit={{ opacity: 0 }} />}
</AnimatePresence>
// GOOD - unique key for each conditional child
<AnimatePresence>
{isOpen && <motion.div key="modal" exit={{ opacity: 0 }} />}
</AnimatePresence>
```
### Do not wrap an already animated component with motion.div
```tsx
// BAD - double animation, transform conflicts
<motion.div animate={{ x: 100 }}>
<motion.div animate={{ x: -50 }}>Content</motion.div>
</motion.div>
// GOOD - single animation level per transform axis
<motion.div animate={{ x: 100 }}>
<motion.div animate={{ opacity: 0.5 }}>Content</motion.div>
</motion.div>
// GOOD - use variants to coordinate parent/child
<motion.div variants={parent} animate="active">
<motion.div variants={child} />
</motion.div>
```
© AThevon · MIT · AThevon/genjutsu
Similar skills
Everything for Frontend and UI →framer-motion by other authors comes first.