Agent skill
framer-motion-animator
Creates smooth animations and micro-interactions using Framer Motion including page transitions, gestures, scroll-based animations, and orchestrated sequences. Use when users request "add animation", "framer motion", "page transition", "animate component", or "micro-interactions".
Install this agent skill to your Project
npx add-skill https://github.com/patricio0312rev/skills/tree/main/frontend/framer-motion-animator
SKILL.md
Framer Motion Animator
Build delightful animations and interactions with Framer Motion's declarative API.
Core Workflow
- Identify animation needs: Entrance, exit, hover, gestures
- Choose animation type: Simple, variants, gestures, layout
- Define motion values: Opacity, scale, position, rotation
- Add transitions: Duration, easing, spring physics
- Orchestrate sequences: Stagger, delay, parent-child
- Optimize performance: GPU-accelerated properties
Installation
npm install framer-motion
Basic Animations
Simple Animation
import { motion } from 'framer-motion';
// Animate on mount
export function FadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
{children}
</motion.div>
);
}
// Animate on hover
export function ScaleOnHover({ children }: { children: React.ReactNode }) {
return (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
>
{children}
</motion.div>
);
}
Exit Animations with AnimatePresence
import { motion, AnimatePresence } from 'framer-motion';
export function Modal({ isOpen, onClose, children }: ModalProps) {
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 bg-black/50 z-40"
/>
{/* Modal */}
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
className="fixed inset-0 z-50 flex items-center justify-center"
>
<div className="bg-white rounded-xl p-6 max-w-md w-full">
{children}
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}
Variants Pattern
Staggered Children
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { type: 'spring', stiffness: 300, damping: 24 },
},
};
export function StaggeredList({ items }: { items: string[] }) {
return (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
>
{items.map((item, index) => (
<motion.li key={index} variants={itemVariants}>
{item}
</motion.li>
))}
</motion.ul>
);
}
Interactive Variants
const buttonVariants = {
initial: { scale: 1 },
hover: { scale: 1.05 },
tap: { scale: 0.95 },
disabled: { opacity: 0.5, scale: 1 },
};
export function AnimatedButton({
children,
disabled,
onClick,
}: ButtonProps) {
return (
<motion.button
variants={buttonVariants}
initial="initial"
whileHover={disabled ? 'disabled' : 'hover'}
whileTap={disabled ? 'disabled' : 'tap'}
animate={disabled ? 'disabled' : 'initial'}
onClick={onClick}
disabled={disabled}
className="px-4 py-2 bg-blue-500 text-white rounded-lg"
>
{children}
</motion.button>
);
}
Page Transitions
Next.js App Router
// app/template.tsx
'use client';
import { motion } from 'framer-motion';
export default function Template({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
);
}
Shared Layout Animations
import { motion, LayoutGroup } from 'framer-motion';
export function Tabs({ tabs, activeTab, onTabChange }: TabsProps) {
return (
<LayoutGroup>
<div className="flex gap-2">
{tabs.map((tab) => (
<button
key={tab.id}
onClick={() => onTabChange(tab.id)}
className="relative px-4 py-2"
>
{activeTab === tab.id && (
<motion.div
layoutId="activeTab"
className="absolute inset-0 bg-blue-500 rounded-lg"
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
)}
<span className="relative z-10">{tab.label}</span>
</button>
))}
</div>
</LayoutGroup>
);
}
Gesture Animations
Drag
export function DraggableCard() {
return (
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
dragElastic={0.2}
dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
whileDrag={{ scale: 1.1, cursor: 'grabbing' }}
className="w-32 h-32 bg-blue-500 rounded-lg cursor-grab"
/>
);
}
Swipe to Dismiss
export function SwipeToDelete({ onDelete, children }: SwipeProps) {
return (
<motion.div
drag="x"
dragConstraints={{ left: 0, right: 0 }}
onDragEnd={(_, info) => {
if (info.offset.x < -100) {
onDelete();
}
}}
className="relative"
>
{children}
<motion.div
className="absolute right-0 inset-y-0 bg-red-500 flex items-center px-4"
style={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
Delete
</motion.div>
</motion.div>
);
}
Scroll Animations
Scroll-Triggered
import { motion, useInView } from 'framer-motion';
import { useRef } from 'react';
export function FadeInWhenVisible({ children }: { children: React.ReactNode }) {
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 50 }}
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 50 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
>
{children}
</motion.div>
);
}
Scroll Progress
import { motion, useScroll, useTransform } from 'framer-motion';
export function ParallaxHero() {
const { scrollY } = useScroll();
const y = useTransform(scrollY, [0, 500], [0, 150]);
const opacity = useTransform(scrollY, [0, 300], [1, 0]);
return (
<motion.div
style={{ y, opacity }}
className="h-screen flex items-center justify-center"
>
<h1 className="text-6xl font-bold">Parallax Hero</h1>
</motion.div>
);
}
export function ScrollProgress() {
const { scrollYProgress } = useScroll();
return (
<motion.div
style={{ scaleX: scrollYProgress }}
className="fixed top-0 left-0 right-0 h-1 bg-blue-500 origin-left z-50"
/>
);
}
Animation Hooks
useAnimate (Imperative)
import { useAnimate } from 'framer-motion';
export function SubmitButton() {
const [scope, animate] = useAnimate();
const handleClick = async () => {
// Sequence of animations
await animate(scope.current, { scale: 0.95 }, { duration: 0.1 });
await animate(scope.current, { scale: 1 }, { type: 'spring' });
// Success animation
await animate(
scope.current,
{ backgroundColor: '#22c55e' },
{ duration: 0.2 }
);
};
return (
<motion.button ref={scope} onClick={handleClick} className="px-4 py-2">
Submit
</motion.button>
);
}
useMotionValue & useTransform
import { motion, useMotionValue, useTransform } from 'framer-motion';
export function RotatingCard() {
const x = useMotionValue(0);
const rotateY = useTransform(x, [-200, 200], [-45, 45]);
const opacity = useTransform(x, [-200, 0, 200], [0.5, 1, 0.5]);
return (
<motion.div
drag="x"
dragConstraints={{ left: -200, right: 200 }}
style={{ x, rotateY, opacity }}
className="w-64 h-96 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl"
/>
);
}
Reusable Animation Components
AnimatedContainer
// components/AnimatedContainer.tsx
import { motion, Variants } from 'framer-motion';
const animations: Record<string, Variants> = {
fadeIn: {
hidden: { opacity: 0 },
visible: { opacity: 1 },
},
fadeInUp: {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
},
fadeInDown: {
hidden: { opacity: 0, y: -20 },
visible: { opacity: 1, y: 0 },
},
scaleIn: {
hidden: { opacity: 0, scale: 0.8 },
visible: { opacity: 1, scale: 1 },
},
slideInLeft: {
hidden: { opacity: 0, x: -50 },
visible: { opacity: 1, x: 0 },
},
slideInRight: {
hidden: { opacity: 0, x: 50 },
visible: { opacity: 1, x: 0 },
},
};
interface AnimatedContainerProps {
children: React.ReactNode;
animation?: keyof typeof animations;
delay?: number;
duration?: number;
className?: string;
}
export function AnimatedContainer({
children,
animation = 'fadeInUp',
delay = 0,
duration = 0.5,
className,
}: AnimatedContainerProps) {
return (
<motion.div
variants={animations[animation]}
initial="hidden"
animate="visible"
transition={{ duration, delay, ease: 'easeOut' }}
className={className}
>
{children}
</motion.div>
);
}
AnimatedList
// components/AnimatedList.tsx
import { motion } from 'framer-motion';
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.05,
},
},
};
const itemVariants = {
hidden: { opacity: 0, x: -20 },
visible: { opacity: 1, x: 0 },
};
interface AnimatedListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
keyExtractor: (item: T, index: number) => string;
className?: string;
}
export function AnimatedList<T>({
items,
renderItem,
keyExtractor,
className,
}: AnimatedListProps<T>) {
return (
<motion.ul
variants={containerVariants}
initial="hidden"
animate="visible"
className={className}
>
{items.map((item, index) => (
<motion.li key={keyExtractor(item, index)} variants={itemVariants}>
{renderItem(item, index)}
</motion.li>
))}
</motion.ul>
);
}
Transition Presets
// lib/transitions.ts
export const transitions = {
spring: {
type: 'spring',
stiffness: 300,
damping: 24,
},
springBouncy: {
type: 'spring',
stiffness: 500,
damping: 15,
},
springStiff: {
type: 'spring',
stiffness: 700,
damping: 30,
},
smooth: {
type: 'tween',
duration: 0.3,
ease: 'easeInOut',
},
snappy: {
type: 'tween',
duration: 0.15,
ease: [0.25, 0.1, 0.25, 1],
},
} as const;
// Usage
<motion.div transition={transitions.spring} />
Reduced Motion Support
import { useReducedMotion } from 'framer-motion';
export function AccessibleAnimation({ children }: { children: React.ReactNode }) {
const shouldReduceMotion = useReducedMotion();
return (
<motion.div
initial={{ opacity: 0, y: shouldReduceMotion ? 0 : 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: shouldReduceMotion ? 0 : 0.5 }}
>
{children}
</motion.div>
);
}
Best Practices
- Use GPU-accelerated properties:
opacity,transform(notwidth,height) - Add
layoutfor smooth resizing: Automatic layout animations - Use
AnimatePresence: For exit animations - Prefer springs: More natural than tween for UI
- Respect reduced motion: Use
useReducedMotionhook - Avoid animating layout thrashing: Don't animate
top,left,width - Use
layoutId: For shared element transitions - Stagger children: For list animations
Output Checklist
Every animation implementation should include:
- Appropriate animation type (simple, variants, gestures)
- Smooth transitions with proper easing
- Exit animations with AnimatePresence
- Reduced motion support
- GPU-accelerated properties only
- Spring physics for natural feel
- Staggered children for lists
- Performance tested on low-end devices
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
rate-limiting-abuse-protection
Implements rate limiting and abuse prevention with per-route policies, IP/user-based limits, sliding windows, safe error responses, and observability. Use when adding "rate limiting", "API protection", "abuse prevention", or "DDoS protection".
rbac-permissions-builder
Implements role-based access control with permission matrix, route guards, policy functions, and UI permission hints. Provides middleware/guards, helper utilities, test suggestions, and permission checking patterns. Use when building "RBAC", "permissions", "access control", or "authorization".
websocket-realtime-builder
Implements real-time features using WebSockets with Socket.io, rooms, authentication, and reconnection handling. Use when users request "real-time updates", "WebSocket", "Socket.io", "live chat", or "push notifications".
webhook-receiver-hardener
Secures webhook receivers with signature verification, retry handling, deduplication, idempotency keys, and error responses. Provides verification code, dedupe storage strategy, runbook for incidents. Use when implementing "webhooks", "webhook security", "event receivers", or "third-party integrations".
auth-module-builder
Implements secure authentication patterns including login/registration, session management, JWT tokens, password hashing, cookie settings, and CSRF protection. Provides auth routes, middleware, security configurations, and threat model documentation. Use when building "authentication", "login system", "JWT auth", or "session management".
rest-to-graphql-migrator
Migrates REST APIs to GraphQL incrementally with schema stitching, REST datasources, and gradual endpoint migration. Use when users request "migrate to GraphQL", "REST to GraphQL", "GraphQL wrapper", or "API modernization".
Didn't find tool you were looking for?