animation-composition: accumulate vs add

Part of CSS Animation Composition & Layering in Core CSS Animation Fundamentals.

The problem

Two effects touch the same transform: an entrance that scales a card from scale(0.9) to scale(1), and a hover that should enlarge whatever the card currently is by a further 10%. The default composition, replace, throws away one of them. Switching to add keeps both — and the card ends up at scale(1.1) on hover, which looks right. Then someone changes the hover to scale(0.1)-style offsets expressed as deltas, or layers two rotations with different origins, and the results stop matching intuition.

add and accumulate both “combine” values, and for many properties they produce the same number. For transform lists, filter lists and a few others they do not, and picking the wrong one produces motion that is subtly — or dramatically — off.

Root cause analysis: appending versus summing

add appends. For list-valued properties such as transform and filter, add produces the underlying list followed by the effect’s list. translateX(40px) added to translateX(10px) becomes translateX(10px) translateX(40px). Transform functions compose by matrix multiplication, so appended functions are applied one after another, each in the coordinate system the previous one left behind.

accumulate sums. For matching functions, accumulate combines the arguments into one function. translateX(40px) accumulated onto translateX(10px) becomes translateX(50px). For translations the visible result is the same as add. The difference appears with functions whose composition is not the same as summing.

Scale is the clearest case. scale(2) added to scale(1.5) appends to scale(1.5) scale(2), which multiplies to an effective scale(3). Accumulating uses the rule for scale, which sums the amounts relative to identity: 1.5 + 2 − 1 = 2.5. Rotation around the same origin gives equal results either way, but a rotate appended after a translate rotates the translated coordinate system, while accumulating a rotate into a list with no matching function falls back to appending.

For numeric properties such as opacity or a registered <number> custom property, both modes simply sum the values — the distinction is about lists and functions.

Combining an underlying value with an effect valueOnly list-valued properties show a difference; numbers sum either way.Combining an underlying value with an effect valueaddaccumulatetranslateX(10px) +translateX(40px)Appended: 50px effectivetranslateX(50px)scale(1.5) + scale(2)Appended: 3x effectivescale(2.5)translate thenrotate(20deg)Rotates moved frameFalls back to appendblur(2px) + blur(4px)Two blurs in sequenceblur(6px)opacity 0.4 + 0.30.70.7
Only list-valued properties show a difference; numbers sum either way.

Step-by-step resolution

Choosing a composition modeDecide per property, from the geometry you want, not from the name.Choosing a composition mode1Write down the underlying value at the moment the second effect runs.Usually the entrance or base state2Write the effect's value as a delta you want applied.The hover, the press, the wobble3If both are translations or numbers, either mode works; prefer add.Most common case4If scaling, use accumulate for additive growth or add for multiplicative growth.2.5x versus 3x in the example5If filters, use accumulate to merge same-function amounts.One blur pass instead of two6Scrub both effects in the Animations drawer and read the Computed transform.
Decide per property, from the geometry you want, not from the name.

Production code pattern

/* Entrance: grows the card into place. */
.card {
  animation: enter 400ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
@keyframes enter {
  from { transform: scale(0.9); }
  to   { transform: scale(1); }
}

/* Hover pulse: adds 0.06 to whatever scale the card has, mid-entrance or not. */
.card:hover {
  animation: enter 400ms cubic-bezier(0.2, 0.8, 0.2, 1) both,
             pulse 600ms ease-in-out infinite alternate;
  animation-composition: replace, accumulate;   /* per-animation, in list order */
}
@keyframes pulse {
  to { transform: scale(1.06); }                 /* accumulate: underlying + 0.06 */
}

/* A soft focus blur layered on a loading blur: one merged filter pass. */
.panel.is-loading { filter: blur(2px); }
.panel.is-loading.is-dimmed {
  animation: deepen 300ms ease-out forwards;
  animation-composition: accumulate;             /* blur(2px) + blur(3px) = blur(5px) */
}
@keyframes deepen { to { filter: blur(3px); } }

@media (prefers-reduced-motion: reduce) {
  .card, .card:hover { animation: none; }
  .panel.is-loading.is-dimmed { animation: none; filter: blur(5px); }
}

Rendering Impact: composite for the scale effects; paint for the blur. Composition happens when the animated value is computed, so the mode does not change the rendering tier — but merging filters with accumulate avoids two sequential blur passes, which halves the filter’s paint work.

The pulse keyframe uses scale(1.06), and with accumulate the effect contributes +0.06 on top of whatever the entrance currently produces. With add the same keyframe would multiply — scale(0.95) scale(1.06) mid-entrance — which is visually close for small factors but diverges quickly for larger ones.

Effective scale at hover, underlying scale 1.5Same keyframes, different composition mode.Effective scale at hover, underlying scale 1.5replace (effect wins)2xaccumulate (sum over identity)2.5xadd (appended, multiplies)3x
Same keyframes, different composition mode.

Verification checklist

Constraints and trade-offs

  • accumulate falls back to appending when the function lists do not match in type and order.
  • Mixing composition modes across many layered effects makes final values hard to reason about; document the intent next to the rule.
  • Matrix interpolation may be used when lists cannot be matched, which can produce unexpected rotations mid-animation.
  • Older engines ignore animation-composition and use replace; the top effect wins there.
  • Individual transform properties (scale, rotate, translate) often remove the need for composition entirely by giving each effect its own property.

Frequently asked questions

When do add and accumulate give different results?

For list-valued properties like transform and filter when the functions do not simply sum, such as scale, or when function lists differ. For plain numbers like opacity both modes sum the values.

Is accumulate just add for numbers?

Yes. For numeric properties both modes add the underlying and effect values. The distinction only matters for lists of functions.

Should I use individual transform properties instead?

Often. If the entrance animates translate and the hover animates scale, putting them on separate properties avoids composition questions entirely. Composition is for effects that must share one property.