When to Use Framer Motion Variants for State‑Driven UI Animations
A concise decision guide for adopting Framer Motion’s Variants to manage complex, reusable animation states in React. Compare variants, direct style updates, and useAnimation, then see a concrete implementation and validation steps.
05 Sept 2026, 14:12 UTC

Problem & Takeaway
In a React app you often need an element to animate between multiple UI states—e.g., a modal opening, a list item expanding, or a button changing color. The core question is: should you write imperative style updates, use the useAnimation hook, or declare a set of Variants on a motion component? Takeaway: Use Variants whenever you have one or more reusable animation states that map directly to component or global state. They keep the code declarative, reduce boilerplate, and let Framer Motion handle transition logic automatically.
Decision & Constraints
Decision: Adopt Variants for any UI element that requires multiple, reusable animation states tied to component or application state.
Constraints:
- Variants only work with Framer Motion components (e.g.,
motion.div,motion.button). - Requires a stable Framer Motion version 5.x or newer.
- Cannot be applied directly to plain HTML elements without wrapping them in a motion component.
Option Comparison
| Approach | Declarative vs Imperative | Reusability | Boilerplate | Performance Impact |
|---|---|---|---|---|
| Variants | Declarative state mapping | High – same variant object reused across components | Low – single variant definition per component | Minimal – internal mapping overhead only when state changes |
| Direct Style Updates | Imperative – set style props manually | Low – logic duplicated per usage | High – many style props spread across renders | Low – trivial CSS updates, but no transition engine |
| useAnimation Hook | Imperative – manual control over animation sequence | Medium – can share animation controller but logic lives in component | Medium – need to call animation.start() each time | Low – direct control, but can lead to duplicated logic |
Trade‑Offs
- Variants reduce boilerplate and keep animation logic in one place, but add a small runtime cost for the internal state map. They also require the element to stay mounted to preserve context.
- Direct Style Updates are quick for single‑shot animations but become hard to maintain when multiple states or shared logic are needed.
- useAnimation offers fine‑grained control and is useful for complex sequences (e.g., staggered children) but can duplicate logic across components.
Concrete Implementation
Below is a minimal example that toggles a card between hidden and visible states using a React useState hook.
import { motion } from 'framer-motion';
import { useState } from 'react';
const cardVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0, transition: { duration: 0.4 } },
};
export function ToggleCard() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(prev => !prev)}>
Toggle Card
</button>
<motion.div
variants={cardVariants}
initial="hidden"
animate={isOpen ? 'visible' : 'hidden'}
style={{ width: 200, height: 120, background: '#f0f0f0', marginTop: 10 }}
>
Card Content
</motion.div>
</div>
);
}
**Where to run**: In a React project with framer-motion installed. Requires npm i framer-motion or yarn add framer-motion. The component can be imported and rendered in any parent component.
Validation Checklist
- Run the app locally (
npm start). - Open the page containing
ToggleCard. - Click the button and observe the card fade in/out and move along the Y axis.
- Open Chrome DevTools, select the
motion.divelement, and verify thatopacityandtransformchange smoothly over ~400 ms. - Optional: Use React Profiler to ensure no significant re‑render cost when toggling
isOpen.
Limitations & Practical Check
- Variants rely on Framer Motion’s internal transition engine; if you reference a CSS property that isn’t supported (e.g.,
filter: blur()on an older browser), the animation may silently fail. - Large nested variant trees can increase bundle size; keep variant objects flat unless deep state mapping is required.
- If you conditionally unmount the motion component, the variant context is lost. Keep the component mounted and control visibility via state instead of conditional rendering.
To confirm the variant is applied, inspect the element’s data-mouse attribute in the dev tools or add onAnimationComplete callbacks to log when transitions finish.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.