CSS sibling-index() replaces your nth-child stagger hacks

Bold text reading 'No Stagger Hacks' beside an isometric machine feeding tiles into a stepped, staggered row.

We need to talk about the “loop of shame.” You know the one. You’re building a grid of WordPress post cards or a product gallery, and the client wants that slick staggered fade-in, with each card appearing 100ms after the one before it. So you open your SCSS file and write a loop that spits out twenty :nth-child rules just to bump a --delay variable.

It’s messy, it’s hard to maintain, and it gets slow as the DOM grows. And with dynamic blocks in Gutenberg, you never really know how many siblings you’ll end up with. The CSS sibling-index() function finally lets you drop this hack.

The manual tax of :nth-child

In the “old” days (last week), a staggered animation without JavaScript meant assigning indices by hand. You either did it in PHP while looping through posts, or in CSS like this:

/* The Loop of Shame */
.card:nth-child(1) { --delay: 1; }
.card:nth-child(2) { --delay: 2; }
.card:nth-child(3) { --delay: 3; }
/* ...and so on until you get bored or the site breaks */

.card {
    animation-delay: calc(var(--delay) * 100ms);
}

If the user adds an 11th item and you only wrote ten rules, that 11th item doesn’t animate properly. CSS sibling-index() just returns an integer for the element’s position, no rules required.

Using CSS sibling-index() for dynamic layouts

The sibling-index() function returns the position of the current element among its siblings. Like :nth-child(), it starts at 1, so the first child returns 1 and the tenth returns 10. Pair it with sibling-count(), which returns the total number of children in the parent, and you can do real math on your layouts.

Here’s the same stagger effect, rewritten:

/* The Modern Way */
.card {
    --index: sibling-index();
    --total: sibling-count();
    
    /* Staggered fade-in */
    animation-delay: calc(sibling-index() * 100ms);
    
    /* Dynamic width based on total siblings */
    width: calc(100% / sibling-count());
}

No loops, no bloated CSS files, and no JavaScript timing bugs. It works the same with 3 items or 300. It’s one of the more useful modern CSS updates for WordPress work.

The gotchas and what’s coming

Right now, sibling-index() counts all siblings. That gets annoying if the parent container also holds spacers or decorative divs. The CSSWG is already working on an of <selector> extension (see CSSWG Issue #9572). Once that lands, you’ll be able to write sibling-index(of .card) and count only siblings with that class.

There are also proposals for children-count() and descendant-count(). sibling-index() looks sideways at peers, and these would look down the tree. For me that’s the exciting part: layout logic can move out of the DOM and back into the stylesheet where it belongs.

If CSS sibling-index() or brittle animations are eating your dev hours, I can take care of it. I’ve been working with WordPress since the 4.x days.

My takeaway

Don’t wait for “perfect” browser support before you learn these. The MDN documentation on sibling-index() covers the implementation details. These functions change how CSS handles counting in the tree, and they’re more than a shorthand. Next time you’re about to write an SCSS loop for an animation delay, use the native function instead.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.