[css-values] Proposal: `children-count()` function
Problem
In the https://github.com/w3c/csswg-drafts/issues/4559 it was resolved to add two new functions which are now in the css-values-5 WD: sibling-count() and sibling-index() as tree-counting functions (I'm using that issue as a template for this one, thanks @argyleink!).
However, these functions only cover a subset of the needs authors have when it comes to knowing the number of items. Sometimes, you want to know that value not on the item, but on its parent (and, sometimes, on another ascendant element — see my separate related proposal — https://github.com/w3c/csswg-drafts/issues/11069).
Proposal
A new function: children-count(). Similar to sibling-count() and sibling-index(), it should report its values based on the element count, and not the node count.
It is a function that is very similar to sibling-count(), but instead of counting siblings counts children.
Similarly, could optionally accept a selector (see https://github.com/w3c/csswg-drafts/issues/9572) as a way to narrow down what is counted as a child.
Example usage:
A square grid based on the children count
.square-grid {
display: grid;
grid-template-columns: repeat(
sqrt(children-count()),
min-content
);
}
Do we still need sibling-count()?
Yes. While we could assign the children-count() to a registered custom property and inherit it on the children, this is rather cumbersome, and a native sibling-count() could be more performant.
Other use cases
- I have the above in some other use cases in my latest article.
- Any place that needs to change the parent's layout based on the children count: bento-layouts, layouts where children are absolutely positioned and we need to set the parent's dimensions, etc.
- I'll update this list with the links as I encounter them.
- https://codepen.io/studiochris/pen/wBKRwzO (using
--item-countto create grid template based on how many items there are)
- https://codepen.io/studiochris/pen/wBKRwzO (using
See also
There is an additional proposal — https://github.com/w3c/csswg-drafts/issues/11069 — descendant-count() that attempts to do a similar thing, but in a more flexible (and complicated for an implementation) way.
I really like the possibility of doing both: counting the total number of items in the item and also in their parent. However, I feel like that's still limiting, why can't we count the number of items from any element and also decide which items get counted?
Take the following HTML as an example:
<div class="total"></div>
<ul>
<li class="dairy">Milk</li>
<li>Eggs</li>
<li class="dairy">Cheese</li>
<li class="dairy">Yogurt</li>
<li>Bacon</li>
<li>Bread</li>
</ul>
If I wanted to count all li elements, sibling-count() would force me to count the items from the item, children-count() from the parent, and there isn't a way to get the count from the .total element or any element outside of the parent. Alternatively, if I just want to count the elements of class .dairy, I couldn't.
I feel like adding more functions for each case is counterintuitive, so why there shouldn't be a general count() function that takes as an argument the selector and returns as an integer the total items?
count(ul > li) /* returns 6 */
count(ul > .dairy) /* returns 3 */
Based on the first examples by Adam Argyle here #4559, it could be changed from:
ul > li {
background-color: hsl(sibling-count() 50% 50%);
}
to:
/* Changes the li color depending on how many .dairy elements are */
li {
background-color: hsl(count(ul > .dairy) 50% 50%);
}
/* Changes the .total element color depending on how many li elements are */
.total {
background-color: hsl(count(ul > li) 50% 50%);
}
/* We could have a keyword to go by the selector from where it's being called */
ul > li {
background-color: hsl(count(selector) 50% 50%);
}
Applying a similar approach to the sibling-index() would have a lot more nuance, since it depends on the selector from where it's been called, but I think it should still be possible to get the index in a sibling group of the same class. Similar to what :nth-of-type does
if I just want to count the elements of class
.dairy, I couldn't.
See #9572 for sibling-count(.dairy)
why there shouldn't be a general count() function
Having to count among all elements in the page seems more expensive than just counting among children or siblings. Also adding/removing one element could invalidate the styles of all other elements, etc.
Thanks for linking that issue, I missed it before.
How expensive would the computation be? I don't think that many counters would be used simultaneously in your average stylesheet, but that's something I can't know for sure.
I must admit that I am talking just from a syntax standpoint, and not from a performance point of view since I think authors would find it messy having to use three kinds of count functions (sibling, children, descendant) for similar but slightly different uses, instead of one function with different arguments.
I upvoted this when I first saw it and just came back to say that in starting to use sibling-index(), I'm finding it a real pain not to have this too.
Up until now, I've always used Pug to do something like this:
- const DATA = [/* an array of objects */];
- const N = DATA.length;
.parent(style=`--n: ${n}`)
- DATA.forEeach((c, i) => {
.child(style=`--i: ${i}`)
- })
This is the pattern I always use. Every single thing I've ever done has needed --n on the parent too. So now I still need to set --n it this way, even if I'm using sibling-index().
Yes. While we could assign the
children-count()to a registered custom property and inherit it on the children, this is rather cumbersome, and a nativesibling-count()could be more performant.
This is yet another sore point with sibling-index(). It starts at 1 and I find I always need it to start at 0, so I still need to use a custom property anyway.
--i: calc(sibling-index() - 1)
I suppose it starts at 1 to be consistent with :nth-child(). Unfortunately, that's not what I need in computations. Plus, sibling-index() is a lot more verbose than --i and that verbosity is a huge readability problem for me.
Re-use what we have. Allow CSS to access CSS counter values or ordered lists as a native performant unique ID engine.
<ol>
<li class="dairy">Milk</li>
<li>Eggs</li>
<li class="dairy">Cheese</li>
<li class="dairy">Yogurt</li>
<li>Bacon</li>
<li>Bread</li>
</ol>
I was reading the “Brand New Layouts with CSS Subgrid” article by @joshwcomeau, and the “Dynamic Data” aside is a great use case for children-count() — an ability to tell the subgrid how many rows it should span based on the number of its children.
Probably worth a separate issue, but I have neither a use case nor a personal need for this: What about a level-count() function to return the amount of nesting without counting all the descendants in those levels like #11069 descendant-count() would?
@kizu, would it make sense to add a filter/match parameter here, similar to how you specified descendant-count() in https://github.com/w3c/csswg-drafts/issues/11069?
That would allow use cases like children-count(ul:not(:last-of-type)).
Apologies if this was already discussed. I read through the thread but didn’t see it addressed.
See #9572 for adding a selector to the existing functions. Of course if it's added there, it will make sense to also add it to future similar functions like this children-count().