Framer Motion
Basic Animation
tsx
1import { motion } from 'framer-motion'
2
3function AnimatedBox() {
4 return (
5 <motion.div
6 initial={{ opacity: 0, y: 20 }}
7 animate={{ opacity: 1, y: 0 }}
8 transition={{ duration: 0.5 }}
9 >
10 Hello World
11 </motion.div>
12 )
13}
Animate Properties
tsx
1<motion.div
2 animate={{
3 x: 100,
4 y: 50,
5 scale: 1.2,
6 rotate: 180,
7 opacity: 0.5,
8 backgroundColor: '#ff0000',
9 borderRadius: '50%',
10 }}
11 transition={{
12 duration: 0.5,
13 ease: 'easeInOut',
14 delay: 0.2,
15 }}
16/>
Transition Types
tsx
1// Spring (default)
2transition={{ type: 'spring', stiffness: 100, damping: 10 }}
3
4// Tween
5transition={{ type: 'tween', duration: 0.5, ease: 'easeInOut' }}
6
7// Inertia
8transition={{ type: 'inertia', velocity: 50 }}
9
10// Ease presets
11ease: 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | 'circIn' | 'circOut' | 'backIn' | 'backOut' | 'anticipate'
Variants
tsx
1const containerVariants = {
2 hidden: { opacity: 0 },
3 visible: {
4 opacity: 1,
5 transition: {
6 staggerChildren: 0.1,
7 },
8 },
9}
10
11const itemVariants = {
12 hidden: { opacity: 0, y: 20 },
13 visible: { opacity: 1, y: 0 },
14}
15
16function List({ items }) {
17 return (
18 <motion.ul
19 variants={containerVariants}
20 initial="hidden"
21 animate="visible"
22 >
23 {items.map((item) => (
24 <motion.li key={item.id} variants={itemVariants}>
25 {item.name}
26 </motion.li>
27 ))}
28 </motion.ul>
29 )
30}
Hover, Tap, Focus
tsx
1<motion.button
2 whileHover={{ scale: 1.05, backgroundColor: '#3b82f6' }}
3 whileTap={{ scale: 0.95 }}
4 whileFocus={{ boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.5)' }}
5>
6 Click me
7</motion.button>
Drag
tsx
1<motion.div
2 drag // Enable both axes
3 drag="x" // Horizontal only
4 dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
5 dragElastic={0.2} // Elasticity outside constraints
6 dragMomentum={true} // Continue after release
7 onDragStart={(e, info) => console.log(info.point)}
8 onDrag={(e, info) => console.log(info.delta)}
9 onDragEnd={(e, info) => console.log(info.velocity)}
10/>
Scroll Animations
tsx
1import { motion, useScroll, useTransform } from 'framer-motion'
2
3function ParallaxSection() {
4 const { scrollYProgress } = useScroll()
5 const y = useTransform(scrollYProgress, [0, 1], [0, -200])
6 const opacity = useTransform(scrollYProgress, [0, 0.5, 1], [1, 0.5, 0])
7
8 return (
9 <motion.div style={{ y, opacity }}>
10 Parallax Content
11 </motion.div>
12 )
13}
14
15// Scroll-triggered animation
16function ScrollTriggered() {
17 return (
18 <motion.div
19 initial={{ opacity: 0, y: 50 }}
20 whileInView={{ opacity: 1, y: 0 }}
21 viewport={{ once: true, margin: '-100px' }}
22 transition={{ duration: 0.6 }}
23 >
24 Appears on scroll
25 </motion.div>
26 )
27}
Layout Animations
tsx
1function LayoutExample() {
2 const [isExpanded, setIsExpanded] = useState(false)
3
4 return (
5 <motion.div
6 layout
7 onClick={() => setIsExpanded(!isExpanded)}
8 style={{
9 width: isExpanded ? 300 : 100,
10 height: isExpanded ? 200 : 100,
11 }}
12 transition={{ layout: { duration: 0.3 } }}
13 />
14 )
15}
16
17// Shared layout
18import { LayoutGroup } from 'framer-motion'
19
20function Tabs() {
21 const [selected, setSelected] = useState(0)
22
23 return (
24 <LayoutGroup>
25 {tabs.map((tab, i) => (
26 <button key={tab} onClick={() => setSelected(i)}>
27 {tab}
28 {selected === i && (
29 <motion.div layoutId="underline" className="underline" />
30 )}
31 </button>
32 ))}
33 </LayoutGroup>
34 )
35}
AnimatePresence (Exit Animations)
tsx
1import { AnimatePresence, motion } from 'framer-motion'
2
3function Modal({ isOpen, onClose }) {
4 return (
5 <AnimatePresence>
6 {isOpen && (
7 <motion.div
8 initial={{ opacity: 0, scale: 0.9 }}
9 animate={{ opacity: 1, scale: 1 }}
10 exit={{ opacity: 0, scale: 0.9 }}
11 transition={{ duration: 0.2 }}
12 >
13 <ModalContent onClose={onClose} />
14 </motion.div>
15 )}
16 </AnimatePresence>
17 )
18}
useAnimate Hook
tsx
1import { useAnimate } from 'framer-motion'
2
3function SequenceAnimation() {
4 const [scope, animate] = useAnimate()
5
6 const handleClick = async () => {
7 await animate(scope.current, { scale: 1.2 })
8 await animate(scope.current, { rotate: 180 })
9 await animate(scope.current, { scale: 1, rotate: 0 })
10 }
11
12 return (
13 <motion.div ref={scope} onClick={handleClick}>
14 Click for sequence
15 </motion.div>
16 )
17}
Motion Values
tsx
1import { motion, useMotionValue, useTransform } from 'framer-motion'
2
3function MotionValueExample() {
4 const x = useMotionValue(0)
5 const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])
6 const background = useTransform(
7 x,
8 [-200, 0, 200],
9 ['#ff0000', '#00ff00', '#0000ff']
10 )
11
12 return (
13 <motion.div
14 drag="x"
15 style={{ x, opacity, background }}
16 />
17 )
18}
Stagger Children
tsx
1const container = {
2 hidden: { opacity: 0 },
3 show: {
4 opacity: 1,
5 transition: {
6 staggerChildren: 0.1,
7 delayChildren: 0.3,
8 },
9 },
10}
11
12const item = {
13 hidden: { opacity: 0, y: 20 },
14 show: { opacity: 1, y: 0 },
15}
16
17<motion.ul variants={container} initial="hidden" animate="show">
18 {items.map((i) => (
19 <motion.li key={i} variants={item} />
20 ))}
21</motion.ul>
Loading Skeleton
tsx
1function Skeleton() {
2 return (
3 <motion.div
4 animate={{ opacity: [0.5, 1, 0.5] }}
5 transition={{ duration: 1.5, repeat: Infinity }}
6 className="bg-gray-200 rounded h-4"
7 />
8 )
9}
Number Counter
tsx
1import { motion, useSpring, useTransform } from 'framer-motion'
2
3function Counter({ value }) {
4 const spring = useSpring(0, { stiffness: 100, damping: 30 })
5 const display = useTransform(spring, (v) => Math.round(v))
6
7 useEffect(() => {
8 spring.set(value)
9 }, [value, spring])
10
11 return <motion.span>{display}</motion.span>
12}