Stagger Animations in React with Motion
By Hizmedia · · 5 min read
A stagger starts a group of animations one after another instead of all at once. It's what makes a list feel like it's being dealt onto the table rather than dumped there, and it quietly tells people what order to look in. Done well it's nearly invisible; done badly it makes them wait. Change the gap and the starting point below to feel the difference.
Try it
The last item starts 540ms after the first, and the whole sequence takes about 0.99s.
import { motion, stagger } from "motion/react";
<motion.ul
initial="hidden"
whileInView="shown"
variants={{ shown: { transition: { delayChildren: stagger(0.06) } } }}
>
{items.map((item) => (
<motion.li key={item.id} variants={{ hidden: { opacity: 0, y: 16 }, shown: { opacity: 1, y: 0 } }} />
))}
</motion.ul>Stagger with variants
In Motion, the parent orchestrates its children through variants. Give the parent delayChildren: stagger(gap) and every child that uses the same variant names starts that much later than the one before it.
import { motion, stagger } from "motion/react";
const list = { hidden: {}, shown: { transition: { delayChildren: stagger(0.06) } } };
const item = { hidden: { opacity: 0, y: 16 }, shown: { opacity: 1, y: 0 } };
export function FeatureList({ features }: { features: string[] }) {
return (
<motion.ul initial="hidden" whileInView="shown" viewport={{ once: true }} variants={list}>
{features.map((feature) => (
<motion.li key={feature} variants={item}>{feature}</motion.li>
))}
</motion.ul>
);
}stagger() replaces the older staggerChildren option, which still works but is deprecated.
Where it starts
stagger(0.06, { from: "last" }) runs the sequence backwards, and from: "center" starts in the middle and spreads outwards, which suits rows of logos or a grid revealing from its centre. You can also pass an index to start from a particular item, such as the one someone clicked.
How long to make it
The total time is the gap multiplied by the number of items, plus one item's own animation. Twenty items at 60 milliseconds means the last one starts more than a second after the first, which feels slow. Keep the whole sequence under about 600 to 800 milliseconds:
- Short lists and button groups: 30 to 60 milliseconds between items.
- Long lists: shrink the gap as the list grows, or only stagger the first eight or so and show the rest together.
- Text revealed word by word: 40 to 80 milliseconds per word; letter by letter: 15 to 30.
Accessibility
Never make people wait for content they came for. Stagger decoration and secondary lists freely, but keep primary content quick. With reduced motion on, show everything at once, or use a short fade without movement. Motion's useReducedMotion hook tells you when to switch.