src/skills/web-animation-framer-motion/SKILL.md
Motion (formerly Framer Motion) animation patterns - motion components, variants, gestures, layout animations, scroll-linked animations, accessibility
npx skillsauth add agents-inc/skills web-animation-framer-motionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use Motion for declarative React animations.
motion.*components for basic animations, variants for orchestrated sequences, AnimatePresence for exit animations,layout/layoutIdfor FLIP animations,useScroll/useInViewfor scroll-triggered effects. Always animate transform properties (x, y, scale, rotate, opacity) for GPU performance. Always respect reduced motion viaMotionConfig reducedMotion="user".
Import:
import { motion } from "motion/react"(v11+ package rename fromframer-motion)
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST wrap exiting components in AnimatePresence for exit animations to work)
(You MUST provide unique key prop to direct children of AnimatePresence)
(You MUST animate transform properties (x, y, scale, rotate, opacity) for GPU-accelerated performance)
(You MUST respect reduced motion preferences using MotionConfig or useReducedMotion)
(You MUST use named constants for all animation timing values - NO magic numbers)
</critical_requirements>
Auto-detection: Motion, Framer Motion, motion.div, motion.button, AnimatePresence, useAnimation, useScroll, useInView, usePageInView, variants, whileHover, whileTap, layoutId, spring, tween, stagger, "motion/react", "framer-motion"
When to use:
When NOT to use:
Key patterns covered:
Detailed Resources:
Motion is a declarative animation library for React that makes animations feel natural and accessible. It uses a physics-based approach with spring animations as defaults, creating fluid motion that matches real-world expectations.
Core principles:
initial, animate, exit props instead of CSS keyframesPrefix any HTML or SVG element with motion. to make it animatable. Use initial, animate, exit, and transition props.
import { motion } from "motion/react";
const FADE_DURATION_S = 0.3;
const SLIDE_DISTANCE_PX = 20;
export const FadeIn = ({ children }: { children: React.ReactNode }) => (
<motion.div
initial={{ opacity: 0, y: SLIDE_DISTANCE_PX }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: FADE_DURATION_S }}
>
{children}
</motion.div>
);
Why good: Named constants, declarative intent, y is GPU-accelerated (never animate top/left/margin)
See examples/core.md Pattern 1 for full examples with className, delay props, and bad examples.
Variants define reusable animation states and enable parent-child orchestration with staggerChildren.
import { motion, type Variants } from "motion/react";
const STAGGER_DELAY_S = 0.1;
const ITEM_DISTANCE_PX = 20;
const containerVariants: Variants = {
hidden: { opacity: 0 },
visible: { opacity: 1, transition: { staggerChildren: STAGGER_DELAY_S } },
};
const itemVariants: Variants = {
hidden: { opacity: 0, y: ITEM_DISTANCE_PX },
visible: { opacity: 1, y: 0 },
};
Children automatically inherit animation state from parent. Use staggerDirection: -1 for reverse stagger on exit.
See examples/core.md Pattern 2 for complete list animation with exit variants.
AnimatePresence enables exit animations for components being removed from the React tree. Direct children must have unique key props.
import { AnimatePresence, motion } from "motion/react";
const MODAL_SCALE_HIDDEN = 0.95;
<AnimatePresence>
{isOpen && (
<motion.div
key="modal"
initial={{ opacity: 0, scale: MODAL_SCALE_HIDDEN }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: MODAL_SCALE_HIDDEN }}
/>
)}
</AnimatePresence>
Animation modes: mode="sync" (default, simultaneous), mode="wait" (wait for exit before enter - ideal for page transitions), mode="popLayout" (for shared layout transitions).
See examples/core.md Pattern 3 for modal and page transition examples.
Gesture props enable hover, tap, focus, and drag interactions.
const HOVER_SCALE = 1.05;
const TAP_SCALE = 0.95;
const GESTURE_SPRING = { type: "spring" as const, stiffness: 400, damping: 17 };
<motion.button
whileHover={{ scale: HOVER_SCALE }}
whileTap={{ scale: TAP_SCALE }}
transition={GESTURE_SPRING}
/>
For drag: use drag, dragConstraints, dragElastic, whileDrag. Use useDragControls for programmatic drag (v12+ adds .stop()/.cancel()).
See examples/core.md Pattern 4 for interactive card and draggable element examples.
The layout prop animates layout changes automatically using FLIP technique. Use layout="position" on children to prevent text distortion. Use layoutId for shared element transitions across different containers.
<motion.div layout transition={LAYOUT_SPRING}>
<motion.h2 layout="position">Title</motion.h2>
</motion.div>
// Shared element: layoutId creates seamless transitions
{activeTab === tab && <motion.div layoutId="indicator" />}
Use LayoutGroup with id prop to scope layoutId to component instances (layoutId is global by default).
See examples/layout.md for expandable cards and tab indicator examples.
whileInView for scroll-triggered animations. useScroll + useTransform for scroll-linked effects.
const REVEAL_DISTANCE_PX = 50;
const PARALLAX_RANGE_PX = 100;
// Scroll-triggered (fires once)
<motion.div
initial={{ opacity: 0, y: REVEAL_DISTANCE_PX }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-100px" }}
/>
// Scroll-linked (continuous)
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] });
const y = useTransform(scrollYProgress, [0, 1], [-PARALLAX_RANGE_PX, PARALLAX_RANGE_PX]);
Motion values from useScroll update without React re-renders.
See examples/scroll.md for progress bar, parallax, and reveal examples.
// Springs - physics-based, natural feel
const BOUNCY = { type: "spring", stiffness: 300, damping: 10 }; // Playful
const SNAPPY = { type: "spring", stiffness: 500, damping: 30 }; // Responsive
const GENTLE = { type: "spring", stiffness: 100, damping: 20 }; // Subtle
// Tweens - duration-based, precise control
const ENTER = { type: "tween", ease: "easeOut", duration: 0.3 }; // Enter
const EXIT = { type: "tween", ease: "easeIn", duration: 0.2 }; // Exit
Rule of thumb: Springs for interactive elements (buttons, cards), tweens for UI transitions (modals, page changes).
See reference.md for full transition type reference with additional presets.
Use useAnimation when you need programmatic control over animations triggered by external events, complex sequences, or start/stop behavior.
const SHAKE_DISTANCE_PX = 10;
const SHAKE_DURATION_S = 0.3;
const controls = useAnimation();
useEffect(() => {
if (hasError) {
controls.start({
x: [0, -SHAKE_DISTANCE_PX, SHAKE_DISTANCE_PX, -SHAKE_DISTANCE_PX, 0],
transition: { duration: SHAKE_DURATION_S },
});
}
}, [hasError, controls]);
<motion.div animate={controls}>{children}</motion.div>
See examples/sequences.md for multi-step sequences and keyframe animations.
Always respect user preferences for reduced motion.
// Site-wide: wrap app root
<MotionConfig reducedMotion="user">{children}</MotionConfig>
// Per-component: custom handling
const FULL_DISTANCE_PX = 50;
const FULL_DURATION_S = 0.5;
const REDUCED_DURATION_S = 0.2;
const shouldReduceMotion = useReducedMotion();
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : FULL_DISTANCE_PX }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: shouldReduceMotion ? REDUCED_DURATION_S : FULL_DURATION_S }}
/>
MotionConfig reducedMotion="user" automatically disables transform/layout animations when reduced motion is preferred. Opacity and color animations still work.
See examples/core.md Pattern 5 for complete accessible animation component.
usePageInView (v12.19+): Detect when page/tab is visible to pause animations or videos in background tabs. Returns boolean, defaults to true on server.
import { usePageInView } from "motion/react";
const isPageVisible = usePageInView();
Enhanced stagger() (v12+): Pass stagger() to delayChildren in variants for from and ease options.
import { stagger } from "motion/react";
// stagger() is passed to delayChildren, NOT staggerChildren
const transition = {
delayChildren: stagger(0.05, { from: "center", ease: "easeOut" }),
};
// from options: "first" (default), "center", "last", or number (index)
Drag Controls (v12+): useDragControls gains .stop() and .cancel() methods.
<red_flags>
High Priority Issues:
MotionConfig reducedMotion="user" or useReducedMotionMedium Priority Issues:
layout="position" on children during parent layout animation - children will distortwillChange - creates GPU layers; Motion handles optimization automaticallyuseAnimation in useEffect - can cause memory leaksGotchas & Edge Cases:
layoutId is global - use LayoutGroup with id prop to scope to component instancesAnimatePresence mode="wait" blocks enter until exit completes - may cause perceived delaywhileInView uses viewport not offset for configuration (unlike useScroll)motion.path, motion.circle, etc. - regular SVG elements won't animateuseInView returns false on server - default to visible state for SSRuseMotionValueEvent for side effectsdrag with layout can conflict - disable layout during drag or use dragListenerstagger() goes on delayChildren, not staggerChildren - they serve different purposes</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST wrap exiting components in AnimatePresence for exit animations to work)
(You MUST provide unique key prop to direct children of AnimatePresence)
(You MUST animate transform properties (x, y, scale, rotate, opacity) for GPU-accelerated performance)
(You MUST respect reduced motion preferences using MotionConfig or useReducedMotion)
(You MUST use named constants for all animation timing values - NO magic numbers)
Failure to follow these rules will break exit animations, cause performance issues, and create inaccessible experiences.
</critical_reminders>
development
Material Design component library for Vue 3
development
VitePress 1.x — Vue-powered static site generator for documentation sites, built on Vite
tools
Docusaurus 3.x documentation framework — site configuration, docs/blog plugins, sidebars, versioning, MDX, swizzling, and deployment
development
TanStack Form patterns - useForm, form.Field, validators, arrays, linked fields, createFormHook, type safety