Back to skills
SKILL.md
Framer Motion Expert
ASecurityUse when Framer Motion 12+ for React. Declarative animations, layout transitions, gestures, scroll-linked motion, AnimatePresence, useAnimate, LazyMotion. Use when building component animations, page transitions, shared layout animations, or gesture-driven UI.
- 5 stars
- 0 votes
- 0 copies
- 1 view
- Added September 27, 2026
Works with
Security analysis
100/100npx -y skills add Harmitx7/tribunal-kit --skill framer-motion-expert --agent claude-codeAre you the author of Framer Motion Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/harmitx7-framer-motion-expert-tribunal-kit)---
name: framer-motion-expert
description: "Use when Framer Motion 12+ for React. Declarative animations, layout transitions, gestures, scroll-linked motion, AnimatePresence, useAnimate, LazyMotion. Use when building component animations, page transitions, shared layout animations, or gesture-driven UI."
version: 5.0.0
last-updated: 2026-09-13
skills:
- motion-engineering
- 60fps-animation
- accessible-animation
tools: Read, Grep, Glob, Bash, Edit, Write
scripts-binding:
- .agent/scripts/lint_runner.js
- .agent/scripts/verify_all.js
---
# Framer Motion 12+ β Dense Reference
---
## π οΈ Technical Architecture & Reference Recipes
---
## π οΈ Technical Architecture & Reference Recipes
---
## π οΈ Technical Architecture & Reference Recipes
---
## Hallucination Traps (Read First)
- β `<Motion>` (capital M) β β
`motion.div` (lowercase dot notation)
- β `motion()` wrapper function β β
`motion.div`, `motion.span`, etc.
- β `exitBeforeEnter` prop β β
`mode="wait"` on `<AnimatePresence>` (removed in FM7+)
- β `exit` works without `<AnimatePresence>` β β
REQUIRES AnimatePresence wrapper
- β `<AnimatePresence>` children without unique `key` β β
ALWAYS set `key`
- β `stiffness + damping` AND `duration + bounce` together β β
pick ONE pair
- β `m.div` without `<LazyMotion>` wrapper β β
REQUIRES LazyMotion parent
- β `layout` animations with `domAnimation` feature set β β
requires `domMax`
- β Force-animating `width`/`height`/`top`/`left` β β
use `x`,`y`,`scale`,`opacity` (GPU)
- β `viewport.once` defaults to true β β
defaults to **false** β add `once: true` for entrance anims
---
## Core Primitives
### `motion.X` / Declarative Animation
```tsx
import { motion } from 'framer-motion';
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3, ease: 'easeOut' }}
/>;
```
### Variants (Stagger / Orchestration)
```tsx
const container = {
hidden: {},
visible: { transition: { staggerChildren: 0.08, delayChildren: 0.1 } },
};
const item = {
hidden: { opacity: 0, y: 20, filter: 'blur(4px)' },
visible: { opacity: 1, y: 0, filter: 'blur(0px)', transition: { duration: 0.4 } },
};
<motion.ul variants={container} initial="hidden" animate="visible">
{list.map(e => (
<motion.li key={e.id} variants={item}>
{e.name}
</motion.li>
))}
</motion.ul>;
```
### Transitions
```tsx
// Tween (default)
transition={{ duration: 0.5, ease: "easeInOut", delay: 0.2, repeat: Infinity, repeatType: "reverse" }}
// Spring (physics)
transition={{ type: "spring", stiffness: 300, damping: 20 }} // OR use duration+bounce, not both
transition={{ type: "spring", duration: 0.8, bounce: 0.25 }}
// Per-property
transition={{ x: { type: "spring", stiffness: 300 }, opacity: { duration: 0.2 } }}
```
---
## Gestures
```tsx
// Hover/Tap/Focus
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
whileFocus={{ boxShadow: "0 0 0 3px rgba(66,153,225,0.6)" }}
transition={{ type: "spring", stiffness: 400, damping: 15 }}
/>
// Drag
<motion.div
drag="x" // "x" | "y" | true
dragConstraints={{ left: -100, right: 100 }}
dragElastic={0.2} // 0=hard stop, 1=free
dragMomentum={true}
dragSnapToOrigin
/>
// Scroll-triggered
<motion.div
initial={{ opacity: 0, y: 50 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.3 }} // β once: true is almost always what you want
/>
```
---
## Layout Animations
```tsx
// layout prop β auto-animates position/size changes
<motion.div layout transition={{ type: "spring", stiffness: 200 }}>
{/* layout="position" = only position, layout="size" = only size */}
</motion.div>
// layoutId β shared element transition (morph between renders)
// List thumbnail β expanded modal:
<motion.div key={item.id} layoutId={`card-${item.id}`} /> // in list
<motion.div layoutId={`card-${selectedId}`} className="modal" /> // in modal
// β TRAP: Cross-tree layoutId requires <LayoutGroup> wrapper
import { LayoutGroup } from "framer-motion";
<LayoutGroup><Sidebar /><MainContent /></LayoutGroup>
```
### AnimatePresence
```tsx
<AnimatePresence mode="sync">
{' '}
{/* "sync"|"wait"|"popLayout" */}
{items.map(item => (
<motion.div
key={item.id}
/* β REQUIRED */ initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
/>
))}
</AnimatePresence>
// mode="wait" β waits for exit before entering
// initial={false} on AnimatePresence β skip first-render animation
```
---
## Scroll Animations
```tsx
import { useScroll, useTransform } from 'framer-motion';
// Page scroll progress (0β1)
const { scrollYProgress } = useScroll();
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
<motion.div style={{ y, opacity }} />;
// Element-scoped scroll
const ref = useRef(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'end start'] });
```
---
## Hooks
### `useAnimate` β Imperative sequences
```tsx
import { useAnimate, stagger } from 'framer-motion';
const [scope, animate] = useAnimate(); // β returns [scope, animate] NOT [ref, controls]
await animate('.item', { opacity: 1 }, { delay: stagger(0.1) });
<div ref={scope}>...</div>;
```
### `useMotionValue` + `useTransform` β No re-renders
```tsx
const x = useMotionValue(0);
const rotateY = useTransform(x, [-200, 200], [-45, 45]);
// β
useMotionValue does NOT trigger React re-renders β key perf advantage over useState
<motion.div style={{ x, rotateY }} drag="x" />;
```
### `useSpring` / `useVelocity`
```tsx
const springX = useSpring(x, { stiffness: 300, damping: 30 });
const xVel = useVelocity(x);
const skewX = useTransform(xVel, [-1000, 0, 1000], [-15, 0, 15]);
```
---
## Performance & Bundle
```tsx
// LazyMotion β ~5KB vs ~30KB full bundle
import { LazyMotion, domAnimation, m } from 'framer-motion';
// domAnimation β 5KB | domMax β 20KB (needed for layout/drag)
<LazyMotion features={domAnimation}>
<m.div animate={{ opacity: 1 }} />
</LazyMotion>;
```
### Accessibility
```tsx
import { useReducedMotion } from 'framer-motion';
const reduce = useReducedMotion();
// opacity/color: always safe | position/scale/rotation: must be disabled when reduce=true
<motion.div
animate={{ x: reduce ? 0 : 100, opacity: 1 }}
transition={{ duration: reduce ? 0 : 0.5 }}
/>;
```
### Rules
- β
Animate: `x`, `y`, `scale`, `rotation`, `opacity` (GPU composited)
- β Never animate: `width`, `height`, `top`, `left`, `padding`, `margin` (causes layout thrashing)
- β
`useMotionValue` for animation-driven values β never `useState`
- β Nest `AnimatePresence` only when necessary β each adds reconciler overhead
- `"use client"` required in Next.js β `motion.div` cannot run in Server Components
Attribution
Comments
Loading commentsβ¦