In CSS it’s possible to style elements based on the number of siblings they have by leveraging the nth-child
selector.
But what if you want to style the parent element based on the number of children? Well, that where the CSS :has()
selector comes into play.
~
# The code
If you’re just here for the code, here it is. You can also see it in action in the demo below.
/* 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;
}
If you want to know how it works, keep on reading 🙂
~
# The selectors
The pattern of each selector built here is this:
parent:has(> count-condition) {
…
}
- By using
parent:has()
we can select theparent
element that meets a certain condition for its children. - By passing
>
into:has()
, we target theparent
’s direct children. - The
count-condition
is something we need to come up with for each type of selection we want to do. Similar to Quantity Queries, we’ll leverage:nth-child()
for this.
☝️ Although the :has()
selector is often called the parent selector, it is way more than that.
At most x
children
By using :nth-child
with a negative -n
it’s possible to select the first x
children. If one of those is also the very last child, you can detect if a parent has up to x
children
/* At most 3 (3 or less, excluding 0) children */
ul:has(> :nth-child(-n+3):last-child) {
outline: 1px solid red;
}
This selector excludes parents that have no children. This is fine in most cases – as any element that only contains text would be matched – but if you do want to include use this selector instead:
/* At most 3 (3 or less, including 0) children */
ul:not(:has(> :nth-child(3))) {
outline: 1px solid red;
}
Exactly x
children
To select item x
from a set you can use :nth-child
without any n
indication. If that child is also the last child, you know there’s exactly x
children in the parent
/* Exactly 5 children */
ul:has(> :nth-child(5):last-child) {
outline: 1px solid blue;
}
At least x
children
To select a parent with a least x
children, being able to select child x
in it is enough to determine that. No need for :last-child
here.
/* At least 10 (10 or more) children */
ul:has(> :nth-child(10)) {
outline: 1px solid green;
}
Between x
and y
children
To do a between selection, you can combine two :has()
conditions together. The first one selects all elements that have x
or more children, whereas the second one cuts off the range by only allowing elements that have no more than y
children. Only elements that match both conditions will be mathed:
/* Between 7 and 9 children (boundaries inclusive) */
ul:has(> :nth-child(7)):has(> :nth-child(-n+9):last-child) {
outline: 1px solid yellow;
}
~
# Demo
See the Pen Styling parent elements based on the number of children with CSS :has() by Bramus (@bramus) on CodePen.
~
# Browser Support
These selectors are supported by all browsers that have :has()
support. At the time of writing this does not include Firefox.
Flipping on the experimental :has()
support in Firefox doesn’t do the trick either. Its implementation is still experimental as it doesn’t support all types of selection yet. Relative Selector Parsing (i.e. a:has(> b)
) is one of those features that’s not supported yet – Tracking bug: #1774588
~
# Spread the word
To help spread the contents of this post, feel free to retweet its announcement tweet:
Style a parent element based on its number of children using CSS `:has()`
🏷 #css #selectors pic.twitter.com/ZMtm4IUD0P
— Bram.us (@bramusblog) November 17, 2022
~
🔥 Like what you see? Want to stay in the loop? Here's how:
Sigh, doesn’t appear to work in FireFox, even with the has option in about:config turned on.
Is it me or can somebody reproduce it?
The implementation in Firefox is still experimental, which is why its behind a feature flag. It has support for some
:has()
selections, but not all.Relative Selector Parsing (i.e.
a:has(> b)
) is one of those features that’s not supported yet. See tracking bug https://bugzilla.mozilla.org/show_bug.cgi?id=1774588Very useful article thank you. You gave me an idea for another article with a different issue actually. So thanks again.