Architecture Diagrams Are Answering the Wrong Question
Engineering Note #01
Engineering Note #04
Last updated · September 6, 2026
Welcome to Motion Animation 101.
Most modern orchestration libraries (like Framer Motion) operate on a declarative mental model. You don't tell the element how to move step-by-step; you describe its states and tell it when to switch.
Here is the breakdown of how animations are actually triggered, structured, and scaled.
initial, animate, and exitEvery basic animation relies on these three lifecycle props. They target the structural state of the component.
initial: The starting line. It defines the visual state of the element before it renders on the screen. If you set this to false, it skips the mounting animation entirely.animate: The destination. The library constantly watches this prop. If the value here changes, the library automatically calculates the transition from the old value to the new one.exit: The parting words. Defines how the element should animate when it's removed from the UI. (Note: This usually requires a wrapping component like <AnimatePresence> to work, because standard React unmounts elements instantly before they can animate).JavaScript
// A simple fade-in-up example
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
/>
animate PropThe most straightforward way to trigger an animation is by passing a dynamic value directly into the animate prop using component state.
You tie your inline styles to a React state variable. When the state changes, a re-render triggers, and the library smooths out the difference.
JavaScript
const [isActive, setIsActive] = useState(false);
return (
<motion.button
animate={{
scale: isActive ? 1.2 : 1,
backgroundColor: isActive ? "#ff0055" : "#0055ff"
}}
onClick={() => setIsActive(!isActive)}
>
Click Me
</motion.button>
);
Variants are the secret sauce of clean animation code. Instead of passing messy object literals directly to the props, you extract your visual states into a separate configuration object.
You define named "states" (like hidden and visible), and then pass those names as strings to initial and animate.
JavaScript
// 1. Define the master blueprint
const boxVariants = {
hidden: { opacity: 0, x: -100 },
visible: { opacity: 1, x: 0, transition: { duration: 0.5 } }
};
// 2. Feed it to the component
<motion.div
variants={boxVariants}
initial="hidden"
animate="visible"
/>
motion component with the same variant names will automatically trigger its own animation without needing explicit state passed down.delayChildren or staggerChildren.JavaScript
const listVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.1 } // Automatically staggers child entry!
}
};
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: { y: 0, opacity: 1 }
};
return (
<motion.ul variants={listVariants} initial="hidden" animate="visible">
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
<motion.li variants={itemVariants} />
</motion.ul>
);
Sometimes you don't want to manage state hooks for basic user interactions. Good animation libraries offer built-in listener props that trigger animations automatically based on user intent.
| Prop | Trigger Condition | Example |
|---|---|---|
whileHover | User moves mouse over the element. | whileHover={{ scale: 1.05 }} |
whileTap | User clicks or presses down on the element. | whileTap={{ scale: 0.95 }} |
whileFocus | Element gains keyboard focus (great for accessibility). | whileFocus={{ target: "2px solid blue" }} |
whileInView | Element enters the browser viewport (Scroll-trigger). | whileInView={{ opacity: 1 }} |
💡 Pro-Tip: Gesture shortcuts play perfectly with variants. You can pass a variant string to a gesture:
whileHover="brighten".
useAnimation)There are times when declarative props fall short—like when you need to trigger an animation after an API call finishes, or sequence a complex chain of non-linear events.
For this, you use imperative controls. This gives you a remote control (controls) that can fire .start() and .stop() functions inside standard JavaScript logic.
JavaScript
const controls = useAnimation();
const handleSaveData = async () => {
await api.submitPayload();
// Trigger animation imperatively once the promise resolves
controls.start({
x: [0, -10, 10, -10, 10, 0], // Shake keyframes
transition: { duration: 0.5 }
});
};
return <motion.form animate={controls} onSubmit={handleSaveData} />;