Framer Motion can animate any React component, even ones that don’t directly support CSS transitions.
Let’s see it in action. Imagine we have a simple Box component that we want to animate its opacity and scale properties.
import React from 'react';
import { motion } from 'framer-motion';
function Box() {
return (
<motion.div
style={{
width: 100,
height: 100,
backgroundColor: 'blue',
borderRadius: 10,
}}
animate={{ opacity: 0.5, scale: 1.2 }}
transition={{ duration: 0.5 }}
/>
);
}
export default Box;
Here, motion.div is the key. Framer Motion exposes motion components for every HTML element and even for SVG elements and custom React components. When you wrap a component with motion., you’re giving it animation capabilities.
The animate prop is where the magic happens. It takes an object describing the target state of the animation. In this case, we want the opacity to go to 0.5 and the scale to go to 1.2. Framer Motion handles the interpolation between the current state and the target state.
The transition prop controls how the animation occurs. Here, we’ve specified a duration of 0.5 seconds. You can also control easing, delay, type of animation (spring, tween), and more.
This simple example illustrates the core principle: identify the element you want to animate, wrap it with motion., and define its target animate state and the transition properties.
Framer Motion is built around a declarative API. You declare what you want to animate to, and Framer Motion figures out the how. This is a significant departure from imperative animation libraries where you’d manually manage animation frames and state updates.
The mental model for Framer Motion is based on states and transitions. A component can be in various states (e.g., "initial," "visible," "hidden," "expanded"). You define animations that move the component between these states.
Consider a common UI pattern: an expandable card.
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
function ExpandableCard() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle Card</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 'auto' }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
style={{ overflow: 'hidden', marginTop: 10 }}
>
<p>This is the content of the card.</p>
</motion.div>
)}
</AnimatePresence>
</div>
);
}
export default ExpandableCard;
Here, AnimatePresence is crucial. It’s a component that enables exit animations for components that are unmounted from the tree. Without it, when isOpen becomes false and the motion.div is removed from the DOM, its animation would be abrupt. AnimatePresence ensures that the exit animation defined on the motion.div is played out.
The initial prop sets the starting state of the animation when the component first mounts. The animate prop defines the state when the component is "active" or in its primary displayed state. The exit prop defines the state when the component is being removed.
The height: 'auto' in the animate prop is a common pattern. Framer Motion intelligently handles animating to auto by measuring the content’s height.
Framer Motion also excels at layout animations. If you have a list of items and add or remove one, Framer Motion can animate the rearrangement of the other items. This is often achieved by adding the layout prop to your motion components.
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
function DraggableList() {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const removeItem = (index) => {
setItems(items.filter((_, i) => i !== index));
};
return (
<div>
{items.map((item, index) => (
<motion.div
key={item}
layout // This enables layout animations
initial={{ opacity: 0, x: -50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 50 }}
transition={{ duration: 0.4 }}
style={{
padding: 10,
margin: 5,
backgroundColor: 'lightgray',
borderRadius: 5,
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
}}
>
{item}
<button onClick={() => removeItem(index)}>Remove</button>
</motion.div>
))}
</div>
);
}
export default DraggableList;
When you click "Remove," the items array changes, and the motion.div corresponding to the removed item will animate out using its exit prop. Crucially, the other motion.div elements will automatically animate to their new positions because of the layout prop. Framer Motion detects the DOM change and smoothly animates the elements to their new positions, maintaining the visual flow.
The layout prop works by observing changes in the DOM and calculating the difference in element positions between frames. It then applies CSS transforms to smoothly transition the elements. This is incredibly powerful for creating dynamic and responsive UIs without manual positioning logic.
A subtle but powerful aspect of Framer Motion is its ability to animate non-layout properties like color, backgroundColor, and boxShadow directly. You don’t need to convert them to a translatable property like transform or opacity. Framer Motion handles the interpolation of these values for you. For instance, animating backgroundColor from 'blue' to 'red' will result in a smooth color blend through purple.
The next frontier is understanding gesture-based animations and the useAnimation hook for more imperative control.