So the :nth-child structural pseudo class is mighty handy already since it lets you select based on mathematical rules. But to my surprise, it got handier and no one told me. Which makes me think no one told you, either:
:nth-child() can do filtering now!
A Quick Primer on Structural Pseudo-classes
So I wrote about the structural pseudo classes thirteen years ago (pikachu shocked face). Structural pseudo-classes are CSS selectors that let you target elements based on the structure of the document. Structural pseudo-classes themselves break into a few categories:
- Algebraic pseudo-classes
- These require an algebraic argument in order select an element by its index within its siblings.
Includes:nth-child(),:nth-last-child(),:nth-of-type() - Ordinal pseudo-classes
- These select elements whose index is initial or final within its siblings
Includes:first-child,:last-child,:first-of-type - Parent-child pseudo-classes
- These select elements based on the relationship parent and child elements have to each other.
Includes:only-child,:only-of-type,:first-of-type,:empty - Document Pseudo-class
- These select based on a fixed point in the document.
Includes:root
If you want to know about all the crazy things you could already do with these, read my article.
:nth-child() Used to Have an Annoying Limitation
Ok, so let’s assume some markup:
<ol> <li>one</li> <li class="foo">two</li> <li>three</li> <li class="foo">four</li> <li>five</li> <li class="foo">six</li> <li>seven</li> <li class="foo">eight</li> </ol>
If you wanted to select the third list item, that’d be li:nth-child(3). Easy peasy lemon-squeezy.
But what if you wanted to select the third .foo?
You’d be tempted to try this:
.foo:nth-child(3) {
outline: 1px solid red;
}
But that’s not gonna work. Because what that selector means is, get the third child amongst the siblings that is also
..foo
So :nth-child was limited to purely the document structure.
Now :nth-child() has “filtering”
Ok, back to the same HTML as before:
<ol> <li>one</li> <li class="foo">two</li> <li>three</li> <li class="foo">four</li> <li>five</li> <li class="foo">six</li> <li>seven</li> <li class="foo">eight</li> </ol>
This time, let’s use this magical of keyword
li:nth-child(3 of .foo) {
outline: 1px solid red;
}
When you use the of keyword, you’re applying a filter.
It’s a filter that tells the browser, reduce the collection to whatever is to the right of
of, then apply the argument on the left of of
Let’s start with a basic live demo where you could go about changing that number:
- one
- two
- three
- four
- five
- six
- seven
- eight
Remember, it’s a filter
Ok, so cool. you can now get the nth-child class of whatever. But you gotta really think about this now.
How many elements do you think will have a yellow background color?
li:nth-child(even of .foo) {
background-color: yellow;
}
If you thought four, you were wrong.
If you thought two, then you win a prize.
Check it out
- one
- two
- three
- four
- five
- six
- seven
- eight
Why are there only two with a yellow background?
Because it first filtered you down to the .foo elements. So the function is now only applying to these:
<ol> <li>one</li> <li class="foo">two</li> <li>three</li> <li class="foo">four</li> <li>five</li> <li class="foo">six</li> <li>seven</li> <li class="foo">eight</li> </ol>
And with those four elements, only two are even:
<ol> <li class="foo">two</li> <li class="foo">four</li> <li class="foo">six</li> <li class="foo">eight</li> </ol>
Because it’s a filter.
It’s a very cool filter.
How cool is it?
Who said you had to give it some basic-ass selector? Not me.
You can give it pretty much whatever you want.
What if you wanted an even foo, but not the last even foo?
li:nth-child(even of .foo:not(:last-child)) {
background-color: yellow;
}
Again, you’re filtering.
You might be wondering, “wait, could I nest my filters?”
Yes, you can nest your filtered nth-child
li:nth-child(2n of :nth-child(3n of .foo)) {
color: yellow;
}
And in case you were wondering, that is the exact same thing as this:
li:nth-child(6n of .foo) {
color: yellow
}
So while you could go about nesting your filters ad infinitum, don’t. It’s best if you keep them as simple as possible.
What are the practical applications?
The most obvious application is tables.
Suppose you’ve got some td and th in a table row. You could start your column styles from the first table cell now instead of the first table item.
Go ahead and change the selector to td:nth-child(even) to see what I mean:
| 1 | one | uno | 一 | yī |
|---|---|---|---|---|
| 2 | two | dos | 二 | èr |
(BTW, you could also pull this off with :nth-of-type, because it’s a type selector. But not if you were trying to go by class name or attributes)
Beyond tables, I’d be wary of doing this too much. I could see some masonry-style shenanigans where it could be fun, though.
Have Fun and Use it Sensibly
The structural pseudo-classes are super handy in cases where content is generated dynamically. In my world of content management, structural pseudo-classes are a way to wrangle the chaos that content authors could produce.
But in any instance where you can just put a class on the element and style off of that, choose that option first. These pseudo-classes could definitely get you in trouble if you’re not careful.