Open ramiy opened 4 years ago
Not sure if nth-children
is a correct term, maybe children-count
?
I will accept any other name as long is the functionality accepted.
Related: https://github.com/w3c/csswg-drafts/issues/4559#issuecomment-562374563 proposes sibling-index()
, sibling-count()
, child-count()
, and tree-depth()
function values.
I think this can be closed as duplicate of https://github.com/w3c/csswg-drafts/issues/4559
It's not a duplicate! #4559 propose new functional notations (CSS values spec). This proposal is for new pseudo-class (CSS selectors spec). Those are two different things.
We can discuss more to decide which approach is better but those are two different approaches. Each has its own advantages and disadvantages.
Edit: we can also accept both proposals.
It's not a duplicate! #4559 propose new functional notations (CSS values spec). This proposal is for new pseudo-class (CSS selectors spec). Those are two different things.
Gotcha
In that case it's the same complexity as :has()
- i.e., it's complex (see eg https://github.com/w3c/csswg-drafts/issues/3345).
:nth-children(2)
would be the same as :has(> :nth-child(2)):not(:has(> :nth-child(3)))
- although I think it's fair to say the latter isn't quite as obvious.
Edit: not quite true actually, :has descends the tree whereas this wouldn't have to. So no, not as complex.
:nth-children(2)
would be the same as:has(> :nth-child(2)):not(:has(> :nth-child(3)))
- although I think it's fair to say the latter isn't quite as obvious.
The new :nth-children(2)
is the same as :has(> :nth-child(2)):not(:has(> :nth-child(3)))
, but with a lower specificity.
Just like :only-child
is the same as :nth-child(1):nth-last-child(1)
, but with a lower specificity.
Yeah, I agree this is not so complicated as :has()
because it doesn't descend. However, it's still a bunch of work and cache misses to walk the DOM if there are tons of children, so that's still not great and probably would need similar caches to what :nth-child
and co have. Gecko does keep track of the child count), but that includes text-nodes (:
Two things that come to mind:
:nth-child()
has the same issue.:nth-child()
and friends came to be). Again, might not be a blocker either, we have tons of other tree-abiding selectors.Is there any chance you could elaborate of what particular styling changes you'd apply based on the number of child elements? The thing I can think of is stuff like changing the width of the parent based on number or such, but that seems brittle / repetitive and better suited by stuff like flex / grid / tables / inline-block etc...
Is there any chance you could elaborate of what particular styling changes you'd apply based on the number of child elements? The thing I can think of is stuff like changing the width of the parent based on number or such, but that seems brittle / repetitive and better suited by stuff like flex / grid / tables / inline-block etc...
@emilio I have several use cases but I will elaborate on a particular example I am currently working on. I'm trying to create CSS framework turning HTML data <table>
into a chart using pure CSS without any JS (for more details checkout ChartsCSS.org).
It is very hard to create a radar chart with pure CSS when you don't know how many <tr>
elements the user will provide. To simplify the explanation think of clip-path
shapes generated using clippy:
/* Triangle */
tbody:nth-children(3) {
clip-path: polygon(50% 0%, 0% 100%, 100% 100%);
}
/* Rhumbus */
tbody:nth-children(4) {
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
/* Pentagon */
tbody:nth-children(5) {
clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
}
/* Hexagon */
tbody:nth-children(6) {
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
}
/* Heptagon */
tbody:nth-children(7) {
clip-path: polygon(50% 0%, 90% 20%, 100% 60%, 75% 100%, 25% 100%, 0% 60%, 10% 20%);
}
/* Octagon */
tbody:nth-children(8) {
clip-path: polygon(30% 0%, 70% 0%, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0% 70%, 0% 30%);
}
/* Nonagon */
tbody:nth-children(9) {
clip-path: polygon(50% 0%, 83% 12%, 100% 43%, 94% 78%, 68% 100%, 32% 100%, 6% 78%, 0% 43%, 17% 12%);
}
/* Decagon */
tbody:nth-children(10) {
clip-path: polygon(50% 0%, 80% 10%, 100% 35%, 100% 70%, 80% 90%, 50% 100%, 20% 90%, 0% 70%, 0% 35%, 20% 10%);
}
This is not the final solution that I will use in the framework but it demonstrates the use-case of conditional design based on the total number of child elements. To do the same with alternative methods will require much more code with different thinking.
@emilio Another example is to add an axes system to a Polar Chart which is like a Pie Chart but with equal slices. To add radial axes you need to know how many child items you have, and the new :nth-children()
pseudo-class will provide the ability to add n axes based on the HTML --> tbody:nth-children(n)
.
Selecting based on how many siblings an element has is an identical amount of work to :nth-last-child()
. (In other words, it's bad, but it's an amount of bad that we've already accepted.) You can tell this is true because :sibling-count()
can be desugared into a combination of :nth-child()
and :nth-last-child()
: :sibling-count(3)
can be desugared to :is(:nth-child(1):nth-last-child(3), :nth-child(2):nth-last-child(2), :nth-child(3):nth-last-child(1))
^_^
Selecting based on how many children an element has is indeed :has()
-equivalent, however.
Awesome idea!
So what would be less complex and not available are:
:root>*>
…, mentioned in #4940)Not sure any of these has sufficient use cases.
Regarding complexity, we have (from hardest to easiest):
:has()
:has-child(S) = :has(> :is(S))
(#4903):nth-children(An+B of S) = :has-child(:nth-child(An+B of S):nth-last-child(1 of S))
(or maybe = :is(:has-child(:nth-child(An+B of S):nth-last-child(1 of S)), :not(:has-child(S)))
if B=0
or A,B≠0, A∣B, A*B<0
)So this one seems the most feasible, and :has()
might be unfeasible (for the web, for now). But if :has-child()
is feasible, I would prefer it over this one.
@tabatkins you label this as "selectors-5". But @Loirooriol wrote that :nth-children()
is the most feasible feature and not complex compare to :has()
and :has-child()
(#4903) which is labeled as "selectors-4". Can you change this issue label to "selectors-4" ?
If/when :has()
gets implemented in Firefox it should be possible to style elements based on number of children,
although a little bit cumbersome:
.container:has(:last-child:nth-child(3)) {
background: green;
}
Extending on @johannesodland’s reply above, I’ve created several selectors leveraging :has()
, :nth-child
and :last-child
to count children:
/* At most 3 (3 or less, excluding 0) children */
ul:has(> :nth-child(-n+3):last-child) {
outline: 1px solid red;
}
/* At most 3 (3 or less, including 0) children */
ul:not(:has(> :nth-child(3))) {
outline: 1px solid red;
}
/* Exactly 5 children */
ul:has(> :nth-child(5):last-child) {
outline: 1px solid blue;
}
/* At least 10 (10 or more) children */
ul:has(> :nth-child(10)) {
outline: 1px solid green;
}
/* Between 7 and 9 children (boundaries inclusive) */
ul:has(> :nth-child(7)):has(> :nth-child(-n+9):last-child) {
outline: 1px solid yellow;
}
Post with the details: https://brm.us/css-has-child-count
Overview
Currently there are no selectors that let us apply a style based on the total amount of direct children the element has.
I can't apply red color if the element that has 2 direct children or green color if it has 3 direct children.
My proposal is to add a new pseudo-class called
:nth-children(n)
which is based on the existing naming conventions.Code Example
Lists:
Tables:
The Problem with Existing Pseudo-Class
The vast majority of the child element selectors are trying to drill up - the selector applied on the
<li>
checking against the parent<ul>
element.While my proposal is to drill down (like flex and grid) - the selector applied on the
<ul>
checking against the amount of child<li>
elements.E:first-child
An E element, first child of its parent.
E:last-child
An E element, last child of its parent.
E:only-child
An E element, only child of its parent.
E:nth-child(n [of S]?)
An E element, the n child of its parent matching S.
E:nth-last-child(n [of S]?)
An E element, the n child of its parent matching S, counting from the last one.
E:empty
An element that has no children (neither elements nor text) except perhaps white space. (This selector is the only selector that applied on the parent element, checking child elements.)
E > F
An F element child of an E element. (This is a general selector to target a specific child element. Doesn't count totals.)
The Solution
As mentioned above, the new selector will behave like flex and grid, it will be applied on the parent element and check the total number of children elements it has.
E:nth-children(n)
An E element, counting total n children.
Usage
Conditional design based on the amount of child elements:
Another example is to use keyword values:
Use Cases
This can help developers apply conditional design in many case:
<td>
elements) and amount of datasets (<tr>
elements).<ol>
,<ul>
,<dl>
based on the total amount of list items.