Framer Motion

AThevon/genjutsu/skills/_jutsu/framer-motion

by AThevon1f518a71378b15d423389e546e767cffc86e14c3No license431 starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 3 days ago

Framer Motion / Motion sub-skill - AnimatePresence, layout animations, gestures, motion values.

Instructions onlySoftware Development
AI-generated overview

Reference guidance for using the Framer Motion / Motion React animation library, covering AnimatePresence, layout animations, gestures and motion values.

What it does
This skill is a reference document for the Framer Motion (now Motion) React animation library. It explains package naming and import choices, when to prefer Framer Motion over GSAP or native CSS, and how to use AnimatePresence exit animations, layout animations, variants and orchestration, gestures, and motion values. It also lists common mistakes to avoid, such as missing keys, unguarded state updates in callbacks, and prop leakage with styled-components or Emotion. It produces guidance and code examples rather than files or runnable artifacts.
When to use it
Use it when implementing or reviewing React UI animations with Framer Motion or Motion, such as modals, toasts, list reordering, shared-layout transitions, drag and hover gestures, or scroll-driven effects. It is also useful when deciding between Framer Motion, GSAP and native CSS for a given animation need.
Requirements
Requires a React project using the motion or framer-motion package (peer dependencies react and react-dom ^18 or ^19). No scripts are included; the skill is instructions plus a reference document. Version-sensitive claims are dated and should be re-verified.

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.jsonImport fromInstall line
motion"motion/react"npm install motion
framer-motion"framer-motion"npm install framer-motion
bothwhichever 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

CriteriaFramer MotionGSAPNative CSS
Layout animationsExcellent (layoutId)ManualImpossible
Exit animationsAnimatePresenceTimeline reverseLimited (display)
Gestures (drag, hover)Native, declarativeDraggable pluginBasic
Scroll-drivenuseScroll + useTransformScrollTrigger (more powerful)scroll-timeline
Complex orchestrationVariants + propagationTimeline (more flexible)@keyframes
Bundle size~50kb tree-shaken~30kb core0kb
React integrationNative, component-firstRefs + useGSAPclassName 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 smoothingconst smoothX = useSpring(x, { stiffness: 300, damping: 30 });
// Scroll trackingconst { scrollY, scrollYProgress } = useScroll();const parallaxY = useTransform(scrollYProgress, [0, 1], [0, -300]);
// Element-scoped scrollconst 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 stateonUpdate={(latest) => setPosition(latest.x)}
// GOOD - guard or useMotionValueEventconst 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>

Source and attribution

Source:AThevon/genjutsuinskills/_jutsu/framer-motionat commit1f518a7

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal