Most developers discover Framer Motion through simple fade-in animations and stop there. But the library has a much deeper set of capabilities — layout animations, shared element transitions, gesture-driven interactions, and orchestration tools that can make your UI feel genuinely alive. Let's go beyond the basics.
Layout Animations
The layout prop is one of Framer Motion's most powerful features. Add it to any element and Framer Motion will automatically animate it whenever its size or position changes — no keyframes needed.
// This list item will smoothly animate when items are added/removed
function TodoItem({ todo, onRemove }) {
return (
<motion.li layout className="todo-item">
{todo.text}
<button onClick={() => onRemove(todo.id)}>Remove</button>
</motion.li>
)
}
Wrap the parent in <AnimatePresence> to also animate items as they leave the DOM:
<AnimatePresence>
{todos.map(todo => (
<motion.li
key={todo.id}
layout
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, x: -20 }}
>
{todo.text}
</motion.li>
))}
</AnimatePresence>
Shared Element Transitions with layoutId
The layoutId prop lets you create seamless transitions between two separate elements — like expanding a card into a modal. Framer Motion treats elements with the same layoutId as the same element and animates between their positions and sizes.
// Card in the grid
function ProjectCard({ project, onClick }) {
return (
<motion.div layoutId={`card-${project.id}`} onClick={onClick}>
<motion.img layoutId={`image-${project.id}`} src={project.image} />
<motion.h3 layoutId={`title-${project.id}`}>{project.title}</motion.h3>
</motion.div>
)
}
// Expanded modal — same layoutIds, different position/size
function ProjectModal({ project, onClose }) {
return (
<motion.div layoutId={`card-${project.id}`} className="modal">
<motion.img layoutId={`image-${project.id}`} src={project.image} />
<motion.h3 layoutId={`title-${project.id}`}>{project.title}</motion.h3>
<p>{project.description}</p>
</motion.div>
)
}
Gesture-Driven Interactions
Framer Motion has first-class support for drag, hover, tap, and pan gestures. These integrate directly with the animation system.
// Draggable card with snap-back
<motion.div
drag
dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
dragElastic={0.2}
whileDrag={{ scale: 1.05, cursor: 'grabbing' }}
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
Drag me
</motion.div>
Scroll-Driven Animations with useScroll
The useScroll hook gives you a reactive scroll progress value you can map to any animatable property using useTransform.
function ParallaxHero() {
const { scrollY } = useScroll()
const y = useTransform(scrollY, [0, 500], [0, -150])
const opacity = useTransform(scrollY, [0, 300], [1, 0])
return (
<section>
<motion.div style={{ y, opacity }} className="hero-bg" />
<motion.h1 style={{ y: useTransform(scrollY, [0, 300], [0, -50]) }}>
Hello World
</motion.h1>
</section>
)
}
Orchestrating Complex Sequences with variants
Variants let you define named animation states and propagate them through a component tree. The parent controls when children animate using staggerChildren and delayChildren.
const container = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
},
},
}
const item = {
hidden: { opacity: 0, y: 20 },
show: { opacity: 1, y: 0 },
}
function AnimatedList({ items }) {
return (
<motion.ul variants={container} initial="hidden" animate="show">
{items.map(i => (
<motion.li key={i.id} variants={item}>
{i.text}
</motion.li>
))}
</motion.ul>
)
}
Performance Tips
- Animate only
transformandopacity— they don't trigger layout recalculation - Use
will-change: transformsparingly on elements that animate frequently - Prefer
useMotionValue+useTransformover state-driven animations for scroll effects — they bypass React re-renders entirely - Use
layout="position"instead oflayoutwhen you only need position animation, not size
Wrapping Up
Framer Motion rewards investment. The more you understand its model — variants, layout animations, motion values — the more expressive and performant your animations become. Start with one pattern from this post and integrate it into a real project. That's the fastest way to make it stick.