Stacking Animations With animation-composition
An icon that floats gently up and down, and also spins when hovered. A notification badge that pulses in scale, and also shakes when a new message arrives. A card that drifts in on a scroll timeline, and also lifts on hover. Each pair is two animations on the same element, and when both touch transform, the second silently replaces the first: the icon stops floating the moment it starts spinning. This page explains why animations on the same property overwrite each other, how the individual transform properties sidestep the problem, and how animation-composition combines animations when they genuinely share a property. It belongs to Keyframe Animation Patterns in the CSS-Only Micro-Interactions & Animations guide.
Why the second animation wins
An element can run several animations at once by listing them: animation: float 3s infinite, spin 600ms. Each animation produces a value for each property in its keyframes. When two animations produce a value for the same property, the browser must choose, and by default it uses the one later in the list — the replace composite operation. The earlier animation keeps running internally, but its effect on that property is hidden.
For transform, that is almost never what a designer intends. float animates transform: translateY(-4px) and spin animates transform: rotate(360deg); while both run, only the rotation shows, and the icon drops back to its un-floated position for the duration of the spin.
The first fix: individual transform properties
The simplest solution is often not to share a property at all. CSS has individual translate, rotate and scale properties, each independent of transform and of each other. An animation on translate and an animation on rotate never conflict; the browser applies them in a fixed order — translate, then rotate, then scale, then the transform property — and both effects are visible.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Stacking animations</title>
<style>
body { font: 14px/1.5 system-ui, sans-serif; margin: 2rem; display: flex; gap: 3rem; }
.icon {
display: grid;
place-items: center;
inline-size: 3.5rem;
block-size: 3.5rem;
border-radius: 12px;
background: #6366f1;
color: #ffffff;
font-size: 1.5rem;
}
/* BROKEN: both animations set transform; on hover the spin replaces the
float, so the icon drops to its resting height while spinning. */
.icon--broken {
animation: float-transform 3s ease-in-out infinite alternate;
}
.icon--broken:hover {
animation: float-transform 3s ease-in-out infinite alternate,
spin-transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes float-transform { to { transform: translateY(-8px); } }
@keyframes spin-transform { to { transform: rotate(360deg); } }
/* FIXED: each animation owns its own individual property. */
.icon--fixed {
animation: float 3s ease-in-out infinite alternate;
}
.icon--fixed:hover {
animation: float 3s ease-in-out infinite alternate,
spin 600ms cubic-bezier(0.22, 1, 0.36, 1);
}
@keyframes float { to { translate: 0 -8px; } }
@keyframes spin { to { rotate: 360deg; } }
@media (prefers-reduced-motion: reduce) {
.icon, .icon:hover { animation: none; }
}
</style>
</head>
<body>
<div class="icon icon--broken" aria-hidden="true">★</div>
<div class="icon icon--fixed" aria-hidden="true">★</div>
</body>
</html>
Hover each icon. The broken one snaps down to its resting height as the spin begins, because the spin's transform replaces the float's. The fixed one keeps floating while it spins, because translate and rotate are separate properties with separate animation values.
One caveat applies to the hover pattern itself: re-declaring animation in the hover state restarts every animation in the list, including the float. The restart is barely noticeable here because the float is gentle, but for more prominent loops, keep the loop on a wrapper element and the hover effect on the child, so the loop is never re-declared.
The key technique: animation-composition for shared properties
Sometimes separate properties are not enough. Two effects may both need scale — a continuous breathing pulse and a one-off pop on click — or both need filter, or the element's own transform must be preserved while an animation adds to it. animation-composition tells the browser to combine an animation's value with the underlying value instead of replacing it.
replace(default) — the animation's value replaces the underlying value.add— the animation's value is added to the underlying value. For transforms, the lists are concatenated, so both are applied in sequence.accumulate— the values are combined numerically. Scale factors combine by adding their differences from 1, lengths and angles simply sum.
The underlying value is whatever the property would be without this animation: the element's own style, plus any animations earlier in the list. That makes composition a way to layer effects in a controlled order.
.badge {
scale: 1;
animation:
breathe 2.4s ease-in-out infinite alternate,
pop 300ms cubic-bezier(0.34, 1.56, 0.64, 1) var(--pop-delay, 0s) both;
/* The second animation adds to whatever the first produces. */
animation-composition: replace, add;
}
@keyframes breathe { to { scale: 1.06; } }
@keyframes pop { 50% { scale: 1.25; } }
@media (prefers-reduced-motion: reduce) {
.badge { animation: none; }
}
animation-composition is a list like the other animation longhands, paired by index with animation-name. Here the breathing loop replaces the element's resting scale as usual, and the pop adds on top of it, so the pop starts from wherever the breathing currently is rather than snapping to a fixed scale.
Order of application
When several transform sources apply to one element — the individual properties, the transform property, and several animations with different composite modes — the result depends on a fixed order, and it helps to know it when effects combine unexpectedly.
The individual properties are always applied in the same sequence: translate first, then rotate, then scale, and finally whatever transform contains. Because transforms compose right to left in their effect on the element's coordinate space, that sequence means scaling happens around the element's own origin before it is rotated and moved, which is almost always what designers expect. It is why a card can scale up on hover with scale while an unrelated animation moves it with translate, without the move distance being scaled.
Within a single property, animations are layered in the order they appear in the animation-name list, each composing onto the result of those before it according to its own animation-composition value. A replace animation later in the list discards everything below it for that property; an add animation builds on it. Putting the "base" loop first and the momentary effects after it, with add, gives the intuitive result that momentary effects happen on top of the loop.
Transitions sit below animations in this stack. A transitioning translate provides the underlying value for any animation on translate, so an add animation can bob an element while a transition slides it to a new position — both visible.
Composition beyond transforms
Composition applies to any animatable property, which enables layering that would otherwise need extra wrapper elements:
- Filters. A continuous hue shift and a one-off brightness flash both on
filtercan coexist withadd, since filter lists concatenate. - Colours and lengths. With
accumulate, an animation can nudge a length relative to the element's current value — "8px more than whatever the layout gave it" — rather than to a fixed number. - Scroll-driven plus time-based. A scroll-linked parallax on
translateand a hover lift on the same property can combine withadd, so hovering a card mid-scroll lifts it from its scrolled position. Scroll-Driven Parallax Effects uses this combination.
Composition does not replace good structure. When effects belong to conceptually different things — a card's position in a list and a hover affordance — separate elements or separate properties remain clearer. Composition is for effects that truly modify the same visual quality.
Browser support
The individual translate, rotate and scale properties are supported in Chrome and Edge 104+, Firefox 72+ and Safari 14.1+. animation-composition is supported in current versions of Chrome, Edge, Firefox and Safari; in an engine without it, the declaration is ignored and animations replace each other as before, so layered effects degrade to the later animation only. Running multiple animations on one element via a comma-separated list has been supported since CSS animations were introduced: Chrome 43, Edge 12, Firefox 16, Safari 9.
FAQ
What happens when two animations target the same property?
By default the later animation in the animation-name list replaces the earlier one's value for that property, so only one effect is visible. animation-composition: add or accumulate changes that, combining the values instead.
What is the difference between add and accumulate?add appends values where the property allows lists, so two transforms become one longer transform list applied in sequence. accumulate combines values numerically, adding lengths, angles and scale factors together. For most transforms both look similar; for filters and scale they can differ.
Can I avoid animation-composition by using separate properties?
Often yes. The individual transform properties translate, rotate and scale each animate independently, so a bobbing translate animation and a spinning rotate animation never conflict. Reach for animation-composition when both effects genuinely need the same property.
Does animation-composition affect performance?
No meaningful difference. Composited properties such as transform and opacity remain on the compositor whether their values are replaced or combined.
Related
- Keyframe Animation Patterns — the parent guide.
- animation-fill-mode: none, forwards, both — what stacked animations leave behind.
- Will-Change and the Compositor Thread — why these stay smooth.
- Motion That Scales With Container Size — composing size-aware motion.
Related articles
More pages in the same section.