Static landing pages convert. Animated landing pages convert better. Subtle motion guides the eye, reinforces hierarchy, and makes interactions feel intentional rather than jarring. Framer Motion is the de facto animation library for React — and in this guide we cover the five animation patterns that matter most for landing pages.
Why Framer Motion?
CSS animations handle simple transitions fine. But once you need scroll-triggered reveals, orchestrated stagger sequences, layout animations, or gesture-driven interactions, pure CSS becomes unwieldy. Framer Motion gives you a declarative API that integrates naturally with React's component model:
npm install motion
Since v11, the package is published as motion (previously framer-motion). Import from "motion/react" in new projects:
import { motion } from "motion/react";
1. Entrance Animations
The most common pattern: fade-and-slide elements in when the page loads. Use initial and animate props on motion components:
"use client";
import { motion } from "motion/react";
const ease = [0.16, 1, 0.3, 1] as const;
export function HeroHeadline() {
return (
<motion.h1
initial={{ opacity: 0, y: 32 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, ease }}
className="text-5xl font-bold tracking-tight"
>
Ship faster with production-ready sections
</motion.h1>
);
}
The cubic bezier [0.16, 1, 0.3, 1] produces a fast-in, slow-out feel that Apple popularized. It feels snappy without being abrupt — far better than the default ease curve for UI entrances.
Use this pattern for hero sections where the headline, subtitle, and CTA button each animate in sequence.
2. Scroll-Triggered Animations
Entrance animations fire on mount. For sections below the fold, you want animations triggered when the user scrolls to them. Framer Motion's whileInView handles this:
<motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}
viewport={{ once: true, margin: "-100px" }}
>
<h2>Features that matter</h2>
<p>Everything you need to ship a landing page in hours, not weeks.</p>
</motion.div>
Key details:
viewport.once: true— the animation fires once and stays in its final state. Without this, scrolling back up resets it, which feels glitchy.viewport.margin: "-100px"— triggers the animation 100px before the element enters the viewport. This way the animation is already in progress when the user sees it.
This is the standard pattern for feature sections and testimonial blocks.
3. Staggered Children
A grid of feature cards animating all at once looks flat. Staggering them — each child appearing 80ms after the previous — creates visual rhythm. Use variants with staggerChildren:
"use client";
import { motion } from "motion/react";
const containerVariants = {
hidden: {},
visible: {
transition: {
staggerChildren: 0.08,
},
},
};
const itemVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5, ease: [0.16, 1, 0.3, 1] },
},
};
export function FeaturesGrid({ features }: { features: { title: string; description: string }[] }) {
return (
<motion.div
variants={containerVariants}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-80px" }}
className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3"
>
{features.map((feature) => (
<motion.div
key={feature.title}
variants={itemVariants}
className="rounded-2xl border border-border bg-card p-6"
>
<h3 className="text-lg font-semibold">{feature.title}</h3>
<p className="mt-2 text-muted-foreground">{feature.description}</p>
</motion.div>
))}
</motion.div>
);
}
The parent defines the orchestration (staggerChildren), each child defines its own animation (itemVariants). The parent's whileInView triggers both the container and all children in sequence. No useEffect, no IntersectionObserver — just declarative props.
4. Page Transitions
Single-page apps can animate between routes using AnimatePresence. In Next.js App Router, wrap your page content in a layout-level AnimatePresence:
"use client";
import { AnimatePresence, motion } from "motion/react";
import { usePathname } from "next/navigation";
export function PageTransition({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
return (
<AnimatePresence mode="wait">
<motion.div
key={pathname}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.25 }}
>
{children}
</motion.div>
</AnimatePresence>
);
}
The mode="wait" prop ensures the exiting page fully fades out before the entering page fades in. Without it, both pages render simultaneously during the transition, causing layout jumps.
Caveat: page transitions in App Router require client components at the layout level, which means those pages lose the benefits of Server Components streaming. Use this pattern selectively — for marketing sites where every page is lightweight, not for data-heavy dashboards.
5. Hover Effects
Interactive elements should respond to hover and tap. Framer Motion's whileHover and whileTap props make this trivial:
<motion.button
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
transition={{ type: "spring", stiffness: 400, damping: 17 }}
className="rounded-xl bg-primary px-6 py-3 text-lg font-semibold text-primary-foreground"
>
Get started
</motion.button>
Spring physics produce a bounce that feels natural. The stiffness: 400, damping: 17 values give a quick, responsive feel without excessive oscillation. For cards, combine hover scale with a subtle shadow lift:
<motion.div
whileHover={{ y: -4, boxShadow: "0 12px 24px rgba(0,0,0,0.1)" }}
transition={{ duration: 0.2 }}
className="rounded-2xl border border-border bg-card p-6"
>
{/* card content */}
</motion.div>
Performance Tips
- Use
transformandopacityonly. Framer Motion animates these on the GPU compositor thread by default. Animatingwidth,height, orbackground-colortriggers layout or paint — both slower. - Add
will-change: transformto elements with complex animations. Framer Motion does this automatically formotioncomponents, but verify in DevTools if you see jank. viewport.once: trueis not just UX — it also disconnects theIntersectionObserverafter firing, reducing runtime overhead on scroll-heavy pages.- Lazy load animated sections below the fold with
next/dynamicso the animation code is not in the initial bundle.
Get Animated Sections Ready to Use
Writing animation code from scratch for every section is time-consuming. The Incubator catalog includes 844+ section variants — heroes, feature grids, testimonials, pricing tables, CTAs, and more — many with Framer Motion animations already wired in. Browse the hero sections and feature sections to see scroll-triggered entrances, staggered grids, and hover effects in action. Copy the code, adjust the content, ship.