Skip to content

Free tool - no signup

Stagger Calculator

Say how many elements you animate, how long one of them takes, and when the whole set has to be finished. The calculator solves for the delay between neighbours and writes the CSS: with sibling-index(), with an --i fallback, or both. Everything runs in your browser and nothing is uploaded.

Delay step
31.8 ms
Last one starts
350 ms
All done at
600 ms

Element 12 starts at 350ms and the set is done at 600ms, exactly on the ceiling.

Preview: 12 elements

Reduced motion is on, so the preview shows the end state and does not play. The numbers and the CSS below are unchanged.

Each bar is one element: where it starts and how long it runs. The vertical line is the ceiling.
CSS to copy
.item {
  --ceiling: 600ms;
  --dur: 250ms;
  --step: 31.8ms; /* tuned for 12 items; used only without sibling-index() */
  animation: rise var(--dur) ease-out both;
  animation-delay: calc(var(--i, 0) * var(--step)); /* --i is zero-based: style="--i: 0" on the first item */
}

@supports (animation-delay: calc(sibling-index() * 1ms)) {
  .item {
    animation-delay: calc(
      (sibling-index() - 1) * (var(--ceiling) - var(--dur))
      / max(1, sibling-count() - 1)
    );
  }
}

@keyframes rise { from { opacity: 0; translate: 0 14px; } }
@keyframes fade { from { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  .item { animation-name: fade; }
}

How to use it

  1. Type the number of elements, the time one of them animates, and the ceiling. The step updates as you type.
  2. Watch the preview. It replays once you stop typing, and Replay runs it again. The timeline under it draws every start and finish on one clock, with the ceiling as a dashed line.
  3. Pick the CSS. Both is the safe default, sibling-index() alone is the shortest, and --i fallback suits markup that already numbers its items. Then copy it.

What it solves

The last element of a staggered set finishes at (count - 1) * delay + duration. Turn that around and you get the step: delay = (ceiling - duration) / (count - 1). Eight tiles that take 200 ms each, inside a 500 ms ceiling, give (500 - 200) / 7 = 42.9 ms. The last tile starts at 300 ms, runs 200 ms and the set is done at 500. Add a ninth tile and the step drops to 37.5 ms while the finish time doesn't move.

That is the whole point of starting from the ceiling. A fixed delay per item answers a question about the first element. Your visitor is asking how long the page makes them wait, and when a list grows from 8 rows to 30, the first answer quietly turns into the second one.

Which CSS should you copy?

sibling-index() is one rule and the item count isn't in it. The browser reads each element's position and the number of siblings, so a longer list keeps the same ceiling. Chrome and Edge ship the functions from 138, Safari from 26.2 and Firefox from 154, which are the dates the sibling-index() stagger article cites from MDN, WebKit and Chrome's release notes.

The --i fallback multiplies a hard-coded step by an index you write into the markup, starting at 0. It works everywhere and it is only right for the number of items it was calculated for, which is why the comment in the output says how many that was.

The Both option puts the fallback in the base rule and the formula inside an @supports block. Why not just write two declarations and let the second win? Because the formula reads var(--ceiling), and a declaration that contains var() isn't checked until the value is computed. We tried it in Chromium 151 with a function the engine doesn't know: the line was accepted, failed at computed-value time, and the resolved delay was 0s, taking the valid fallback above it down too. The two-line pattern in the article works because its second line has no var() in it. A feature query is the version that keeps working once it does.

Edge cases the tool reports instead of hiding

One element has nothing to stagger against, so the step is 0. An animation as long as the ceiling, or longer, leaves no room for delays; the step is clamped to 0 and the generated formula wraps its room in max(0ms, ...) so it can't go negative. And when neighbours start less than one frame apart, under 16.7 ms at 60 Hz, they begin in the same frame and move in clumps. The calculator doesn't fix that for you. It tells you, because the honest answers are fewer staggered items or a longer ceiling, and the article shows how to cap the count with min().

Reduced motion

With prefers-reduced-motion: reduce set, the preview above doesn't play. The elements sit in their end state, the Replay button disappears, and the timeline still shows when each one would have started. The CSS it prints does the same job in your stylesheet: the rise is swapped for a plain fade, so new content still announces itself without travelling.

Common questions

Why is the element count missing from the sibling-index() CSS?
Because the browser supplies it. sibling-count() reads the number of siblings from the document, so the same rule fits a list of five and a list of fifty. The count only matters to this tool, which needs it to tell you the step in milliseconds. The --i version is the opposite: it has the step typed in, so it is only right for the count it was calculated for.
What does the calculator do when the animation is longer than the ceiling?
It sets the step to zero and says so. The formula would return a negative number, and a negative animation-delay does not postpone anything: it starts the animation part-way through its timeline, so the first elements would jump into view half finished. The set then ends when one animation ends, which is later than the ceiling you asked for. Raise the ceiling or shorten the animation.
Why does the preview draw only 40 elements?
A preview that fits in an article column cannot show 300 distinct bars anyway. The first 40 are drawn at their real delays, so a long list still shows its true step, and the numbers, the timeline scale and the CSS all use the full count.
Why is the step rounded to a tenth of a millisecond?
It is the value printed into the --i fallback, and a tenth is fine enough that the set lands within a fraction of a millisecond of the ceiling for ordinary lists. On a very long list the rounding adds up: 299 gaps of a step rounded from 19.23 ms to 19.2 ms finish 9 ms early. When the drift reaches a millisecond the tool prints the fallback finish time next to the exact one.
Do hidden elements count in the delay?
In the sibling-index() version, yes. The function counts elements in the document, so an item hidden with display: none keeps its place in the sequence and leaves a gap in the wave. The article linked below shows the check. Remove filtered rows from the DOM instead of hiding them if the gap matters.
Does anything I type get uploaded?
No. The arithmetic and the preview run in your browser tab, and there is no request, account or stored state.

The behaviour of the functions, with a running demo, is in staggered CSS animation with sibling-index(), and the arithmetic comes from the guide to easing curves. To change a rule and watch it move, open the live CSS preview. And if the wave stutters rather than flows, the cause is usually the property you animate, covered in blocking the main thread. When scroll replaces the clock, the question changes from "how many milliseconds apart" to "over which stretch of the scroller", and the scroll timeline playground answers that one.