Animating Stacked Boxes Like Apple Music’s Hits Playlist Cover

Apple Music’s Spatial Audio playlists ship with a distinctive cover: a pink container holding boxes that fade in and out as they scale toward the edges. It’s a loop that repeats forever, with each box entering at the center, growing, and fading away. That effect is straightforward to reproduce in pure CSS once you break it down.

Animated GIF showing the Apple Music UI we are recreating. It's brightly colored shades of pink against a dark gray background with information about the playlist to the right of the pattern, and options to play and shuffle the sings in orange buttons.

Markup and Base Styling

The HTML is minimal: a wrapping container plus 10 .box elements, all absolutely positioned so they stack at a common starting point.

<div class="container">
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <!-- etc. -->
</div>

From measurements taken off the original artwork, the container is roughly 315px wide and 385px tall. The lightest shade in the outer area is #eb5bec; the darkest color inside a box is #471e45. The container gets position: relative, center-aligned contents via grid, overflow: hidden to contain the expanding boxes, and a subtle border-radius to match the rounded corners of the source artwork.

.container {
  background-color: #eb5bec;
  height: 315px;
  width: 385px;
}
.container {
  background-color: #eb5bec;
  height: 315px;
  position: relative;
  width: 385px;
}
.container {
  background-color: #eb5bec;
  display: grid;
  height: 315px;
  place-items: center;
  position: relative;
  width: 385px;
}
.container {
  background-color: #eb5bec;
  height: 315px;
  overflow: hidden;
  position: relative;
  width: 385px;
}
.container {
  background-color: #eb5bec;
  border-radius: 16px;
  height: 315px;
  position: relative;
  width: 385px;
}

Box Layout and Look

Each .box is a 100px square, positioned absolutely within the grid-centered container. Since the boxes fade out while growing, starting them at full opacity means they obscure one another when stacked; a base opacity: 0.5 keeps the composition readable while still letting later boxes show through earlier ones.

.box {
  background: #471e45;
  height: 100px;
  position: absolute;
  width: 100px;
}
.box {
  background: #471e45;
  height: 100px;
  opacity: 0.5;
  position: absolute;
  width: 100px;
}

Keyframe Animation

The animation itself needs only a start and end state. From that initial opacity: 0.5, the boxes transition to 0 (fully transparent) while scaling up to container size. The scale factor 3.85 is chosen deliberately: 100px × 3.85 equals the container’s 385px height, producing a smooth, linear growth that ends exactly at the edges.

@keyframes grow {
  from {
    /* do stuff */
  }
  to {
    /* do stuff */
  }
}
@keyframes grow {
  from {
    opacity: 0.5;
    transform: scale(0);
  }
  to {
    opacity: 0;
    transform: scale(3.85);
  }
}

Applying the animation is simple enough—declare it with a linear timing function and infinite iterations:

.box {
  animation: grow 10s linear infinite; /* 10s = 10 boxes */
  /* etc. */
}

But without stagger, all 10 boxes animate in lockstep, looking like a single expanding square. Vanilla CSS has no loop construct, so each box’s delay is set individually via a custom property that starts at one second and is overridden per element:

.box {
  --delay: 1s;
  
  animation-delay: var(--delay);
  /* same as before */
}
.box:nth-child(2) {
  --delay: 2s;
}
.box:nth-child(3) {
  --delay: 3s;
}
.box:nth-child(4) {
  --delay: 4s;
}
.box:nth-child(5) {
  --delay: 5s;
}
/* five more times... */

That stagger turns the syncopated boxes into the cascading, layered effect seen on the playlist cover—nothing but markup and stylesheets.