Stagger animations provide an interesting effect where multiple ordered elements animate sequentially with a slight delay between each, rather than all animating at once. This technique is often used in lists, galleries, or navigation menus to guide the user's eye and add a polished, rhythmic feel to interactions.
Stagger animations with sibling-index()
Use the sibling-index() property on the animation-delay property so that the animation on each element is offset by a number proportionate to their position in their parent. The sibling-index() function returns an integer, so it must be multiplied by a time unit to convert it to a time.
#stagger-list > .item {
--stagger-time: 0.1s;
/* Define the animation first */
animation: fade-in 0.4s;
/* Set the `animation-delay` to a time multiplied by the `sibling-index()` */
animation-delay: calc(sibling-index() * var(--stagger-time))
}
MANDATORY: Respect user preferences by disabling the animation for users who prefer reduced motion.
@media (prefers-reduced-motion: reduce){
/* Disable animation for users who prefer reduced motion. */
#stagger-list > .item {
animation: none;
}
}
Fallback strategies
Test for support for sibling-index() using CSS with @supports (animation-delay: calc(sibling-index() * 0.1s)){} or JavaScript with !CSS.supports('animation-delay: calc(sibling-index() * 0.1s)').
To support stagger animations in older browsers, use JavaScript to add a --sibling-index custom property to each sibling element. MANDATORY: wrap this in a CSS.supports('animation-delay: calc(sibling-index() * 0.1s)') test to avoid running unneeded JavaScript.
if(!CSS.supports('animation-delay: calc(sibling-index() * 0.1s)')){
const staggerList = document.getElementById('stagger-list');
[...staggerList.children].forEach((el, index)=>el.style.setProperty('--sibling-index', index + 1));
}
Add an animation-delay declaration that uses the --sibling-index custom property. It must be before the animation-delay declaration that uses the sibling-index() function. This does not need to be wrapped in @supports - older browsers will not parse the second declaration and will use the first declaration.
#stagger-list > .item {
animation-delay: calc(var(--sibling-index) * var(--stagger-time));
animation-delay: calc(sibling-index() * var(--stagger-time));
}