On August 18, 2026, Firefox 154 shipped sibling-count() and sibling-index(). That finished the set. Chrome and Edge had them since 138, Safari since 26.2, and MDN now files both under "newly available since August 2026", with the web-features dataset recording the same Baseline date.
What Reached Baseline in August 2026
The twelve cards above run one rule. There's no --i in the markup, no :nth-child ladder in the stylesheet, and no script. Each card asks the browser where it sits among its siblings, turns that number into a delay, and the last card lands exactly at the 600 ms ceiling the rule sets. Open the editor under the demo and change --ceiling to 1200ms. The whole wave stretches, and nothing else needs touching.
Why does that matter for motion work? Because stagger is the technique everyone reaches for and nobody wants to maintain. The earlier piece on easing curves ended its stagger section with arithmetic: pick the ceiling first, then divide. Until this summer, applying that arithmetic in CSS meant the stylesheet couldn't see how many items it was styling. Now it can.
TL;DR:
sibling-index()returns an element's 1-based position among its siblings andsibling-count()returns how many there are. Put them inanimation-delayand a staggered list needs one rule. Chrome 138, Safari 26.2 and Firefox 154 all support them. Hidden elements still count, and a browser that lacks the functions just skips the stagger.
The Three Old Ways to Stagger
Before the functions existed, a stagger came from one of three places, and each one leaked.
The first is the inline index. You write style="--i: 3" on every item and the CSS multiplies it: animation-delay: calc(var(--i) * 40ms). It's the cleanest of the three and I'd still call it the best old answer. But the markup now carries presentation. Every template, every React map, every CMS loop has to emit the number, and a reordered list has to re-emit all of it.
The second is the :nth-child ladder. A Sass loop prints twelve rules, each with a hard-coded delay. Item thirteen arrives with no delay at all, because nobody wrote rule thirteen. Quietly, the longer your list gets, the worse the animation looks.
The third is JavaScript: measure the children, write a delay onto each. It works until the first paint happens before the script does, and then the cards flash in unstaggered and jump.
None of them can express the one thing that actually matters, which is a total time budget. A per-item delay of 100 ms is a decision about item one. Twenty items later it's a decision about a 2.2-second wait.
A fixed per-item delay is a bug waiting for a longer list.
How sibling-index() Reads the Tree
The CSS Working Group's draft of CSS Values and Units Level 5 (editor's draft of September 4, 2026) defines both functions as returning an <integer>. sibling-count() is the number of child elements in the parent. sibling-index() is the element's position among its inclusive siblings, and like :nth-child() it starts at 1, not 0.
That integer type is the useful part. It drops straight into calc(), which means it multiplies with lengths, times and angles. MDN's own example is one line: animation-delay: calc(1s * sibling-index()).
Running the snippets on this page in Chromium 152 turned up the behaviour the specs imply but don't spell out:
- Text nodes and comments between items are ignored. Two list items with stray text around them returned positions 1 and 2.
- Inserting a new first item recomputed every delay. The old twelfth card became the thirteenth and its delay moved from 318 ms to 350 ms.
- A lone child gets index 1 and count 1, so the formula below divides by zero unless you guard it. Chromium resolved the unguarded version to a delay of 0s, which is harmless, but the
max()in the demo says the intent out loud.
Stagger With a Ceiling, Not a Delay
Here's the formula from the easing guide, turned into CSS. The last item finishes at (count - 1) * delay + duration. Solve for the delay and you get the ceiling-first version:
.card {
--ceiling: 600ms;
--dur: 250ms;
animation: rise var(--dur) cubic-bezier(0, 0, 0.58, 1) both;
animation-delay: calc(
(sibling-index() - 1) * (var(--ceiling) - var(--dur))
/ max(1, sibling-count() - 1)
);
}
@keyframes rise { from { opacity: 0; translate: 0 14px; } }
Read it from the inside out. (var(--ceiling) - var(--dur)) is the time left for delays once the last item has its own 250 ms. Dividing it by sibling-count() - 1 gives the step between neighbours. Multiplying by sibling-index() - 1 places each item on that ladder, with the first item at zero.
The curve is cubic-bezier(0, 0, 0.58, 1), which is ease-out spelled out, because arrivals should leave the gate fast. The step that comes out depends only on how many items the list has:
| Items | Step between neighbours | Last item starts at |
|---|---|---|
| 5 | 87.5 ms | 350 ms |
| 12 | 31.8 ms | 350 ms |
| 20 | 18.4 ms | 350 ms |
| 40 | 9.0 ms | 350 ms |
The right-hand column is the point. Same stylesheet, same ceiling, and the finish time never moves. In the Chromium 152 check the computed delays for twenty cards were 0 ms, 18.4 ms for the second card and 350 ms for the last.
Forty items at 9 ms apart is a smear, not a wave, and nobody perceives card thirty as part of a sequence. So cap it. The same functions work inside min():
animation-delay: calc(
(min(sibling-index(), 8) - 1) * (var(--ceiling) - var(--dur))
/ max(1, min(sibling-count(), 8) - 1)
);
With twelve items this gave delays of 0, 50, 100 and so on up to 350 ms for the eighth card, and the remaining four arrived with the eighth. That's the "stagger the first eight, let the rest land together" rule from the easing guide, now living in the stylesheet instead of in somebody's head.
Where the Index Counts Things You Can't See
This is the catch, and it's the sort of catch that ships to production because the demo list never gets filtered.
sibling-index() counts elements in the DOM. It doesn't know what's visible. In the check, hiding the fourth and fifth of twelve cards with display: none left every other delay exactly where it had been: 95 ms, 127 ms, 159 ms and on up. The hidden cards still owned their slots, so the wave had a hole in it.
Try it in the demo. Add .card:nth-child(6) { display: none } to the editor and watch for the stumble where card six used to arrive. Two practical rules fall out of that:
- If a filter or search hides rows, remove them from the DOM or re-render the list. Hiding is not the same as being absent.
- Keep the staggered items in a parent of their own. A heading or a button in the same parent takes position 1, and every card slides down one place.
Is that a flaw? Honestly, no. It's the same rule :nth-child() has always followed, and matching it is what keeps the two predictable. But it's the first thing to check when a stagger looks uneven.
Beyond Delay: Fans, Widths and Exits
The functions aren't only for animation-delay. Anything that takes a number can take them. Three patterns pass in Chromium 152:
/* A fan: rotate each item around the middle of the set */
.fan li {
rotate: calc((sibling-index() - (sibling-count() + 1) / 2) * 6deg);
}
/* Equal widths with no flex or grid */
.tab { width: calc(100% / sibling-count()); }
/* Close in reverse: the last item leaves first */
.menu li {
transition: opacity 150ms ease-in;
transition-delay: calc((sibling-count() - sibling-index()) * 30ms);
}
Five fan items came out at -12, -6, 0, 6 and 12 degrees. The equal-width pattern is the example WebKit's Safari 26.2 post gives for sibling-count(). The reverse-exit delays for twelve items ran from 330 ms down to 0 ms, which is the closing sequence a nav drawer wants.
Support Today, and the Fallback That Costs Nothing
| Engine | First release with it | Date on the primary source |
|---|---|---|
| Chrome and Edge | 138 | June 24, 2025 |
| Safari | 26.2 | WebKit feature post, December 12, 2025 |
| Firefox | 154 | August 18, 2026 |
Dates come from the Chrome 138 release notes, WebKit's Safari 26.2 post and MDN's Firefox 154 notes. The web-features dataset puts the Baseline date at 2026-08-18, the day Firefox shipped. Baseline "newly available" means the newest browsers have it, not the whole installed base, so check your own analytics before you rely on it.
Do you need an @supports block? Usually not. A browser that doesn't know sibling-index() treats the whole declaration as invalid and throws it away, and animation-delay falls back to 0. The cards then arrive together. Feeding a made-up function name to CSS.supports() returned false, which is the same parse failure an older engine would hit. An unstaggered fade-up is an acceptable default.
If you'd rather keep the stagger on older browsers, declare the old way first and the new way second:
.card {
animation-delay: calc(var(--i, 0) * 30ms);
animation-delay: calc((sibling-index() - 1) * 30ms);
}
A browser that rejects the second line keeps the first. In a Chromium check, an invalid second declaration left the first in force at 0.09 s. Inline --i stays in the markup only for as long as you support those visitors, and then it can go.
Reduced Motion: Keep the Fade, Drop the Travel
The demo ends with a second design of the same animation. When the visitor has asked for reduced motion, animation-name swaps from rise to fade:
@media (prefers-reduced-motion: reduce) {
.card { animation-name: fade; }
}
@keyframes fade { from { opacity: 0; } }
The cards still arrive in order, so the cue that something new appeared survives. The 14 px of travel is gone. That's the distinction the easing guide drew: the request is to reduce motion, not to remove feedback. The stagger itself can stay, since a short opacity wave isn't what triggers vestibular symptoms; translation, scale and spin are. If your wave also moves things, shorten the ceiling or drop the delay too.
Where This Fits in a Motion Stack
Scroll-driven and scroll-triggered effects get most of the 2026 attention, and our Chrome 150 piece sorts out which of those shipped when. Stagger is the quieter win. It's one of the most common entrance techniques, and until this summer the CSS version had a hole where the item count should be. The CSS versus GSAP comparison lists a 12-card entrance stagger as a case where CSS and a JavaScript library both hold 60 fps on its test phone, which is the strongest argument for the no-library route. Randomised stagger is the next thing browsers are trying: Safari Technology Preview 251 added CSS random() support, but no stable browser ships it, so index-based delays remain the safe choice today. And if your list is long enough to stall the frame, main thread budgets matter more than delays do.
Summary
sibling-index() and sibling-count() are 1-based integer functions that read an element's place in its parent. They reached all three engines with Firefox 154 on August 18, 2026, after Chrome 138 in June 2025 and Safari 26.2 in December 2025. Inside animation-delay, they let one rule turn a time ceiling into the right step for any list length: (index - 1) * (ceiling - duration) / (count - 1). Cap the count with min() for long lists. Hidden elements still count, so remove filtered rows from the DOM. Older browsers drop the declaration and show an unstaggered animation, and prefers-reduced-motion should swap the travel for a fade rather than delete the effect.