Master React Micro-Interactions with Framer Motion
Functional UI is only half the battle. The real magic lies in Micro-interactions—subtle animations that turn a generic website into a premium product. Let’s learn how to implement them in React using Framer Motion, from absolute basics to advanced layouts.
Why Framer Motion Over Traditional CSS?
While CSS transitions and keyframes are great for simple loops, they quickly become messy when handling complex component lifecycles (like when an element unmounts from the DOM).
Framer Motion is built specifically for React. It uses a declarative syntax, meaning you just tell it what state you want to animate to based on props or state, and it handles the physics-based transitions under the hood.
Setting Up the Environment
First, let’s install the package into your React/Next.js project:
Bash
npm install framer-motion
1. The Core Concept: The motion Component
Framer Motion works by wrapping standard HTML elements with a motion proxy. This unlocks specialized props like initial, animate, whileHover, and whileTap.
Let’s build a high-fidelity interactive button that uses spring physics instead of rigid time durations:
JavaScript
import { motion } from "framer-motion";
export const InteractiveButton = () => {
return (
<motion.button
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
whileHover={{
scale: 1.05,
backgroundColor: "#4f46e5",
boxShadow: "0px 10px 20px rgba(79, 70, 229, 0.3)"
}}
whileTap={{ scale: 0.95 }}
transition={{
type: "spring",
stiffness: 400,
damping: 15
}}
className="px-6 py-3 bg-indigo-600 text-white font-semibold rounded-lg shadow-md transition-colors"
>
Explore Projects
</motion.button>
);
};
Breaking Down the Props:
initial: The starting state before the component mounts. Here, it starts hidden (opacity: 0) and pushed down slightly (y: 20).animate: The target state when the component renders. It fades in and glides up smoothly.whileHover&whileTap: Built-in event listeners. Notice how we smoothly scale up on hover, change the color, add a glowing box-shadow, and shrink it slightly on click for a satisfying tactile feel.transition: Instead of a boring linear duration, we used aspringvisual.stiffnesscontrols the speed/force, anddampingcontrols the bounciness.
2. Advanced: Orchestrating Lists (Variants)
When a recruiter loads your portfolio, stagger-animating your project cards looks highly professional. We achieve this using Variants. Variants allow parent elements to coordinate animations for their children.
Here is how to create a staggered grid container:
JavaScript
import { motion } from "framer-motion";
// 1. Define animation layouts
const containerVariants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.2, // Delays each child by 0.2s
},
},
};
const cardVariants = {
hidden: { opacity: 0, scale: 0.9, y: 30 },
show: {
opacity: 1,
scale: 1,
y: 0,
transition: { type: "spring", stiffness: 300, damping: 20 }
},
};
// 2. Implement components
export const ProjectGrid = ({ projects }) => {
return (
<motion.div
variants={containerVariants}
initial="hidden"
animate="show"
className="grid grid-cols-1 md:grid-cols-3 gap-6 p-6"
>
{projects.map((project) => (
<motion.div
key={project.id}
variants={cardVariants}
className="bg-white dark:bg-zinc-900 p-6 rounded-xl shadow-lg border border-gray-100 dark:border-zinc-800"
>
<h3 className="text-xl font-bold mb-2">{project.title}</h3>
<p className="text-gray-600 dark:text-gray-400">{project.description}</p>
</motion.div>
))}
</motion.div>
);
};
Why Recruiters Care
When a technical interviewer reviews this code in your GitHub repository, it shows them that you understand modern UI orchestration, declarative layout cycles, and advanced user experience principles.
