Mastering Framer Motion Variants: Reusable States, Staggered Children, and Responsive Design
Framer Motion variants let you define reusable animation states, coordinate child animations with staggering, and adapt to different screen sizes—all in one place. Learn how to set them up, test, and trade‑off performance for maintainability.
09 Apr 2026, 02:40 UTC

Why Framer Motion Variants Matter
When building interactive UIs in React, you often find yourself writing the same animation logic over and over. Framer Motion’s variants feature lets you define those common animation states once and reuse them across any motion component. This not only cuts boilerplate but also guarantees consistent behavior when the UI changes.
Defining Reusable Animation States
A variant is a plain JavaScript object that maps state names (e.g., hidden, visible) to style objects. The same variant can be applied to multiple components, and you can switch between states by changing the animate prop.
const cardVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.3 } },
};
Apply it to a motion.div:
import { motion } from "framer-motion";
function Card({ isOpen }) {
return (
<motion.div
variants={cardVariants}
initial="hidden"
animate={isOpen ? "visible" : "hidden"}
exit="hidden"
>
<h3>Card Title</h3>
<p>Card content goes here.</p>
</motion.div>
);
}
Changing cardVariants updates every card instantly—no need to touch each instance.
Coordinated Child Animations with Staggering
Variants shine when you need a parent to orchestrate its children. By adding a transition object to the parent variant, you can set staggerChildren or delayChildren to create a cascading effect.
const listVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15, // 150ms between each child
delayChildren: 0.1, // start after 100ms
},
},
};
Each child uses the same itemVariants:
const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: { y: 0, opacity: 1, transition: { duration: 0.2 } },
};
Resulting component:
function ItemList({ items, isOpen }) {
return (
<motion.ul
variants={listVariants}
initial="hidden"
animate={isOpen ? "visible" : "hidden"}
>
{items.map((i) =>
<motion.li key={i.id} variants={itemVariants}>
{i.text}
</motion.li>
)}
</motion.ul>
);
}
Now the list items appear one after another without any manual timers.
Responsive Variants for Different Screen Sizes
Framer Motion accepts responsive values in variants. You can provide an array of values that map to breakpoints defined in your CSS or use useMediaQuery to set a flag that switches variants.
const responsiveBox = {
hidden: {
scale: [0.8, 1], // 0.8 on mobile, 1 on desktop
opacity: 0,
},
visible: {
scale: [1, 1.05],
opacity: 1,
transition: { duration: 0.4 },
},
};
In practice, you might write:
import { useMediaQuery } from "react-responsive";
function ResponsiveBox({ isOpen }) {
const isMobile = useMediaQuery({ maxWidth: 767 });
return (
<motion.div
variants={responsiveBox}
initial="hidden"
animate={isOpen ? "visible" : "hidden"}
>
<h4>Responsive Box</h4>
</motion.div>
);
}
The same variant definition automatically adapts to the viewport.
Trade‑Offs and Practical Checks
- Readability vs. Size: Large variants can become unwieldy. Keep them focused; split into smaller objects if they grow beyond ~10 properties.
- Performance: Every variant change triggers a re‑render of the component tree. For massive lists, consider
presenceorAnimatePresenceto mount/unmount only visible items. - Debugging: Use the
debugprop onmotioncomponents to log state transitions to the console. Verify thatinitialandanimatevalues match the variant keys you defined. - Responsive Arrays: The array length must match the number of breakpoints you intend. If you provide fewer values, Framer Motion will reuse the last value for remaining breakpoints.
To confirm a variant works:
- Run
npm startand open the component in the browser. - Toggle the
isOpenstate via a button. - Open Chrome DevTools, go to the Console tab, and look for logs if you added
debug. - Inspect the element’s computed style to ensure the expected CSS values are applied during each state.
Actionable Takeaway
Start by extracting the most repetitive animations in your project into a variants object. Apply it to a motion component, then use staggerChildren on parents to coordinate lists or grids. Finally, add responsive arrays to make the same animation feel natural on mobile and desktop. With variants, a single change in your animation logic instantly propagates throughout your UI, saving time and reducing bugs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.