How to Use CSS animation-composition

The CSS animation-composition property defines how an element's animated keyframe values interact with its underlying base styles or other running animations. By specifying one of three values—replace, add, or accumulate—developers gain precise control over whether an animation completely overwrites an existing property value, stacks alongside it, or mathematically combines with it. This guide explains how each mode works, how they differ, and how to use them effectively in modern web design.


The Problem It Solves

By default, CSS animations completely override any base styles applied to an element for the targeted property. For example, if a base class applies transform: translateX(50px) and an animation defines transform: rotate(45deg), the translation is lost during the animation because the rotation replaces it. Historically, developers had to redefine the base styles inside every keyframe. The animation-composition property solves this by allowing keyframes to dynamically combine with an element's existing properties.


The Three Composition Values

The animation-composition property accepts three keyword values:

animation-composition: replace;
animation-composition: add;
animation-composition: accumulate;

1. replace (Default Behavior)

The replace value is the standard behavior of CSS animations. The keyframe value replaces the underlying base property value entirely.

.box {
  transform: translateX(100px);
  animation: spin 3s infinite;
  animation-composition: replace;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
  • Result: At the start of the animation, translateX(100px) is replaced by the initial animation state. The element rotates, but the 100px translation is ignored.

2. add (Stacking Values)

The add value appends the keyframe value to the underlying base value, creating a composite list. The animated value acts as an addition to the existing style chain from left to right.

.box {
  transform: translateX(100px);
  animation: spin 3s infinite;
  animation-composition: add;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
  • Result: The browser evaluates the final transform as transform: translateX(100px) rotate(360deg). The element maintains its original 100px offset while performing the rotation.

For space-separated or comma-separated properties (such as filter or box-shadow), add appends the animation values onto the existing list:

.card {
  filter: blur(2px);
  animation: brighten 2s infinite alternate;
  animation-composition: add;
}

@keyframes brighten {
  to {
    filter: brightness(1.5);
  }
}
  • Result: The computed filter becomes filter: blur(2px) brightness(1.5).

3. accumulate (Combining Values Mathematically)

The accumulate value combines the underlying value and the animated value into a single, merged value whenever mathematically possible. Instead of appending to a list, it performs an arithmetic addition.

Consider an element that already has a blur filter applied:

.image {
  filter: blur(5px);
  animation: pulse-blur 2s infinite alternate;
  animation-composition: accumulate;
}

@keyframes pulse-blur {
  to {
    filter: blur(10px);
  }
}
  • Result: With accumulate, the values merge arithmetically. At the end of the keyframe, the computed filter is filter: blur(15px) (5px + 10px), rather than a stacked list of two separate blur functions.

The Difference Between add and accumulate

The distinction between add and accumulate is clearest when animating matching transform functions or multi-value units:

  • Using add on identical transform functions: If the base style is transform: scale(2) and the keyframe is transform: scale(1.5), add produces transform: scale(2) scale(1.5). The browser multiplies the matrices sequentially.
  • Using accumulate on identical transform functions: Using accumulate on the same styles combines them arithmetically. The base scale(2) and animated scale(1.5) sum their delta from the default state, resulting in a single scale(2.5).

If the values cannot be combined arithmetically—such as combining a rotate() with a scale()—accumulate falls back to behaving like add.


Application in Multiple Animations

The animation-composition property can also accept a comma-separated list to handle multiple animations applied to a single selector:

.element {
  transform: translateX(50px);
  animation: 
    slide 2s infinite,
    pulse 1s infinite;
  animation-composition: add, accumulate;
}

In this setup, the slide animation stacks onto the base transform using add, while the pulse animation combines with the preceding states using accumulate. This granular control allows complex, layered UI micro-interactions without writing redundant CSS rules.