sibling-index() and sibling-count() — position-aware CSS without the :nth-child() ladder
Styling elements by position has always meant telling CSS something it should already know. A staggered list entrance was a ladder of :nth-child(2), :nth-child(3), :nth-child(4) rules with hand-bumped delays. Or a --i custom property stamped onto every item in the markup. Or a JavaScript loop calling style.setProperty after render. All three break the same way: add, remove, or reorder an item and the numbers are wrong.
sibling-index() and sibling-count() hand those numbers to the stylesheet. The first returns the element's position among its siblings, starting at 1 like :nth-child(). The second returns how many element children its parent has. Both are integers, not strings, so they slot straight into calc() — multiply by a time for animation delays, by a percentage for widths, by an angle for hues or rotations.
A stagger becomes one declaration:
.list > li {
animation-delay: calc((sibling-index() - 1) * 80ms);
}
Subtracting 1 keeps the first item at zero delay. The same offset matters for color wheels: hues wrap at 360deg, so 360deg / sibling-count() * sibling-index() lands the last item on the same hue as the first. Distribute with (sibling-index() - 1) instead and the spread stays even.
The coolest part? These are live values. Insert or remove a sibling and every other element's computed style updates...and because normal transitions animate computed-value changes, the whole group glides to its new state. Delete a row and the remaining rows re-widen and re-color themselves. Deal a card into a fan built on the midpoint (sibling-count() + 1) / 2 and every card rotates to rebalance. That's behavior you'd previously have written a resize-observer's worth of JavaScript for.
As I write this, support is one day old: Firefox 154 shipped both functions yesterday, joining Chrome and Edge 138 from June 2025 and Safari 26.2 from December 2025. MDN still labels sibling-index() limited availability because the Baseline data hasn't caught up with a release this fresh, so ship the fallback: a JS loop that stamps --i only when CSS.supports() says the browser needs it, and a doubled declaration where the older browser uses the variable and newer ones parse the native function.
The demo keeps every number in CSS. Add and remove pipeline stages to watch widths and hues redistribute, replay the entrance stagger, and deal cards into a fan that re-centers itself. React edits the DOM; the stylesheet does the counting.
Tree counting functions
Every element knows where it stands
Add and remove items. The stagger, the hues, the widths, and the fan are all computed in CSS from sibling-index() and sibling-count(). React only edits the DOM — there are no inline styles, numbered classes, or per-item scripts here.
Supported: every color, width, delay, and angle below is derived from each element’s live position in its parent.
This browser does not support sibling-index() yet. The rows keep a single accent color and full width, and the cards sit in a flat row.
Index × count
One rule, every row knows its share
calc(100% / sibling-count() * sibling-index())- Lint
- Typecheck
- Build
- Test
- Bundle
Change the count and watch every existing row glide to its new width and hue. The computed values change, so ordinary transitions animate the redistribution.
Symmetry from the midpoint
A hand of cards that rebalances itself
Each card rotates away from (sibling-count() + 1) / 2. Deal or take a card and the whole fan re-centers.
- A
- K
- Q
- J
- 10