How to Wait for the sibling-count() and sibling-index() Functions
The new features of CSS do not appear out of thin air, but go through a long process: discussion, evaluation, definition, writing, prototyping, testing, publishing, support, and more. This process is very long, and even if we are eager to use new features, we can only wait.
But we can choose a different way of waiting: completely avoid interfaces or demonstrations using this feature? Or challenge the boundaries of CSS and try to implement it yourself?
Many enterprising and curious developers have chosen the latter. Without this mentality, CSS will stagnate. So today we will explore two upcoming functions: sibling-count()
and sibling-index()
. We have been looking forward to them for many years, so let me let my curiosity fly and feel their charm in advance!
Tree Count Function
You may have needed to know where an element is in its sibling element, or how many child elements a certain element has in order to calculate in CSS, such as implementing some interleaving animations, each element has a longer delay, or changing the background color of the element based on the number of sibling elements. This has long been a long-awaited project on my CSS wishlist. Check out this 2017 CSSWG GitHub Issue:
Function request. It would be great to be able to use the
calc()
function within thecounter()
function. This will bring new possibilities to the layout.
However, the counter()
function uses a string, which makes it useless in the calc()
function that handles numbers. We need a similar set of functions that return the index of an element and its number of sibling elements in the form of an integer . This does not seem to be an over-demand. We can currently use the :nth-child()
pseudo-selector (and its variants) to query elements based on tree locations, not to mention using the :has()
pseudo-selector to query based on the number of items the element contains.
Luckily, this year CSSWG approved the implementation of sibling-count()
and sibling-index()
functions! Some content has been written in the specification:
TheThe
sibling-count()
function represents the total number of child elements in the parent element of the element using the function, expressed as<integer></integer>
.
sibling-index()
function represents the index of the element using the function in the child element of its parent element, represented by<integer></integer>
. Similar to:nth-child()
,sibling-index()
counts from 1.
How long will it take for us to use them? Earlier this year, Adam Argyle said: "A Chromium engineer mentioned wanting to do this, but we don't have the logo to try it out yet. I'll share it when there is news!" So, while I'm hoping to get more news in 2025, we probably won't see them release anytime soon. Meanwhile, let's see what we can do now!
Update (March 5, 2025): Chrome has submitted the intention to prototype these two functions.
Original method
In terms of syntax and usage, the closest thing to a tree count function is the custom properties. However, the biggest problem is how to populate them with the correct indexes and counts. The easiest and longest way is to hardcode each value using pure CSS: we can use the nth-child()
selector to give each element its corresponding index:
li:nth-child(1) { --sibling-index: 1; } li:nth-child(2) { --sibling-index: 2; } li:nth-child(3) { --sibling-index: 3; } /* 以此类推... */
Setting sibling-count()
equivalents requires more subtleties because we need to use the number of :has()
selectors to query. Quantity query has the following syntax:
.container:has(> :last-child:nth-child(m)) { }
… where m is the number of elements we want to locate. It works by checking if the last element of the container is also the nth element we position; therefore it has only so many elements. You can use this tool from Temani Afif to create your own quantity query. In this case, our quantity query looks like this:
ol:has(> :nth-child(1)) { --sibling-count: 1; } ol:has(> :last-child:nth-child(2)) { --sibling-count: 2; } ol:has(> :last-child:nth-child(3)) { --sibling-count: 3; } /* 以此类推... */
For brevity, this example deliberately contains only a small number of elements, but as the list grows, it will become difficult to manage. Maybe we can use preprocessors like Sass to write them for us, but we want to focus on pure CSS solutions here. For example, the following demonstration can support up to 12 elements, and you can already see how ugly the code is.
For those who score, it takes 24 rules to understand the index and count of 12 elements. We certainly feel like we can reduce this number to more manageable numbers, but if we hardcode each index, we increase the amount of code we write. The best thing we can do is rewrite our CSS so that we can nest the --sibling-index
and --sibling-count
properties together. Instead of writing each attribute separately:
li:nth-child(2) { --sibling-index: 2; } ol:has(> :last-child:nth-child(2)) { --sibling-count: 2; }
We can nest the --sibling-count
rules within the --sibling-index
rules.
li:nth-child(2) { --sibling-index: 2; ol:has(> &:last-child) { --sibling-count: 2; } }
Although it seems strange to nest the parent element inside its child element, the following CSS code is completely valid; we are selecting the second li
element, inside, if the second li
element is also the last one, we are selecting a ol
element, so there are only two elements on the list. Which syntax is easier to manage? It depends on you.
But this is just a little improvement. If we have 100 elements, we still need to hardcode the --sibling-index
and --sibling-count
properties 100 times. Fortunately, the following method will add rules in a logarithmic way, especially with base 2. So instead of writing 100 rules for 100 elements, we only need to write about 100 rules for about 100 elements.
Improvement method
This method was first described by Roman Komarov in October last year, where he prototyped the two tree count functions and the future random()
functions. This is a great post, so I highly recommend you read it.
This method also uses custom properties, however, instead of hard-coded each property, we will use two custom properties to build the --sibling-index
property of each element. To be consistent with Roman's articles, we call them --si1
and --si2
, both starting at 0:
li:nth-child(1) { --sibling-index: 1; } li:nth-child(2) { --sibling-index: 2; } li:nth-child(3) { --sibling-index: 3; } /* 以此类推... */
The real --sibling-index
will be constructed using these two properties and a factor (F), which represents an integer greater than or equal to 2, which tells us the number of elements that can be selected according to the formula sqrt(F) - 1
. So...
- For factor 2, we can select 3 elements.
- For factor 3, we can select 8 elements.
- For factor 5, we can select 24 elements.
- For factor 10, we can select 99 elements.
- For factor 25, we can select 624 elements.
As you can see, increasing the factor by 1 will increase the number of elements we can choose exponentially. But how does this translate into CSS?
The first thing to know is that the formula for calculating the attributes of --sibling-index
is calc(F * var(--si2) var(--si1))
. If our factor is 3, it looks like this:
.container:has(> :last-child:nth-child(m)) { }
The selector below may be random, but please be patient with me to explain. For the --si1
attribute, we will write a rule for elements selected as multiples of the factor and offset them by 1 until F - 1 is reached, and then set --si1
as the offset. This translates to the following CSS:
ol:has(> :nth-child(1)) { --sibling-count: 1; } ol:has(> :last-child:nth-child(2)) { --sibling-count: 2; } ol:has(> :last-child:nth-child(3)) { --sibling-count: 3; } /* 以此类推... */
So if our factor is 3, we will write the following rules until we reach F-1, that is, 2 rules:
li:nth-child(2) { --sibling-index: 2; } ol:has(> :last-child:nth-child(2)) { --sibling-count: 2; }
For the --si2
attribute, we will write rules for selecting elements with the number of elements in batches as factors (so if our factor is 3, we will select 3 elements per rule), moving backward from the last possible index (8 in this case) until we can no longer select more elements in batches. This is a bit complicated to write in CSS:
li:nth-child(2) { --sibling-index: 2; ol:has(> &:last-child) { --sibling-count: 2; } }
Similarly, if our factor is 3, we will write the following two rules:
li { --si1: 0; --si2: 0; }
That's it! By setting only the values --si1
and --si2
, we can calculate up to 8 elements. The mathematical calculations behind how it works look weird at first glance, but once you understand it intuitively, everything is clear. I made this interactive demo where you can see how to access all elements using this formula. Hover over the code snippet to see which elements you can select and click each code snippet to combine them into a possible index.
If you adjust elements and factors to maximum, you will see that we can select 48 elements with only 14 code snippets!
etc., there is one thing missing: sibling-count()
function. Luckily, we will reuse everything we learned from sibling-index()
prototyping. We will start with two custom properties: --sc1
and --sc1
in the container, and both start with 0 as well. The formula for calculating --sibling-count
is the same.
li:nth-child(1) { --sibling-index: 1; } li:nth-child(2) { --sibling-index: 2; } li:nth-child(3) { --sibling-index: 3; } /* 以此类推... */
Roman's article also explains how to write selectors for --sibling-count
attributes separately, but we will use the :has()
selection method in our first technique so we don't have to write additional selectors. We can stuff these --sc1
and --sc2
properties into the rules we define sibling-index()
properties:
.container:has(> :last-child:nth-child(m)) { }
This is using factor 3, so we can calculate up to eight elements with only four rules. The following example has a factor of 7, so we can calculate up to 48 elements with only 14 rules.
This method is great, but probably not for everyone because it works almost magical or simply because you don't find it aesthetically pleasing. While this is easy for those who are keen to make fire with flint and steel, many people cannot ignite their fire.
JavaScript Method
For this approach, we will again use custom properties to simulate the tree count function, and best of all, we will write less than 20 lines of code to calculate to infinity-or I would say 1.7976931348623157e 308, which is the limit for double precision floating point numbers!
We will use the Mutation Observer API, so of course JavaScript is required. I know it's like admitting failure to many people, but I don't agree. If the JavaScript method is simpler (which is indeed true in this case), then it is the most suitable choice. By the way, if performance is your main concern, stick to hard-code each index in CSS or HTML.
First, we will get our container from the DOM:
ol:has(> :nth-child(1)) { --sibling-count: 1; } ol:has(> :last-child:nth-child(2)) { --sibling-count: 2; } ol:has(> :last-child:nth-child(3)) { --sibling-count: 3; } /* 以此类推... */
We will then create a function that sets the --sibling-index
attribute in each element and --sibling-count
in the container (it will be available for child elements due to the cascade). For --sibling-index
, we have to iterate over elements.children
, and we can get elements.children.length
from --sibling-count
.
li:nth-child(2) { --sibling-index: 2; } ol:has(> :last-child:nth-child(2)) { --sibling-count: 2; }
Once we have our function, remember to call it once so that we have the initial tree count property:
li:nth-child(2) { --sibling-index: 2; ol:has(> &:last-child) { --sibling-count: 2; } }
Lastly, Mutation Observer. We need to initialize a new observer using the MutationObserver
constructor. It accepts a callback function that is called every time the element changes, so we wrote the updateCustomProperties
function. Using the generated observer object, we can call its observe()
method, which accepts two parameters:
- The elements we want to observe, and
- A configuration object that defines what we want to observe through three Boolean properties:
attributes
,childList
, andsubtree
. In this case we just want to check for changes inchildList
, so we set the property totrue
:
li:nth-child(1) { --sibling-index: 1; } li:nth-child(2) { --sibling-index: 2; } li:nth-child(3) { --sibling-index: 3; } /* 以此类推... */
This is all we need to do! Using this method we can calculate many elements, in the following demonstration I set the maximum value to 100, but it easily reaches ten times:
So yes, that's our flamethrower. It certainly ignites the flame, but for the vast majority of use cases, it's too powerful. But that's what we have when we wait for the perfect lighter.
What will you do next?
I don't have a time machine, so I can't say when the sibling-index()
and sibling-count()
functions will be published. However, CSSWG has written something in the specification, and the browser (mainly Chromium) intention to publish things has been very strong lately, so I believe we will see these two functions soon!
.container:has(> :last-child:nth-child(m)) { }
- Possible Future CSS: Tree-Counting Functions and Random Values (Roman Komarov)
- View Transitions Staggering (Chris Coyier)
- Element Indexes (Chris Coyier)
- Enable the use of counter() inside calc() #1026
- Proposal: add sibling-count() and sibling-index() #4559
- Extend sibling-index() and sibling-count() with a selector argument #9572
- Proposal: children-count() function #11068
- Proposal: descendant-count() function #11069
The above is the detailed content of How to Wait for the sibling-count() and sibling-index() Functions. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

It's out! Congrats to the Vue team for getting it done, I know it was a massive effort and a long time coming. All new docs, as well.

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That's like this.

I'd say "website" fits better than "mobile app" but I like this framing from Max Lynch:

If we need to show documentation to the user directly in the WordPress editor, what is the best way to do it?

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

There are a number of these desktop apps where the goal is showing your site at different dimensions all at the same time. So you can, for example, be writing

CSS Grid is a collection of properties designed to make layout easier than it’s ever been. Like anything, there's a bit of a learning curve, but Grid is

I see Google Fonts rolled out a new design (Tweet). Compared to the last big redesign, this feels much more iterative. I can barely tell the difference
