Recently, I’ve seen a recurring error with how :nth-child()
and :nth-of-type()
selectors are being used, so I wanted to host an anonymous algebra moment on these, my favorite CSS Level 3 selectors.
Frequently, I see :nth-child()
selectors used for making columns:
From here, I see a lot of selectors like this:
:nth-child(1n)
:nth-child(2n)
:nth-child(3n)
Here’s why that is incorrect: the browser starts counting at 1 (the first item) and checks item each to see if that element matches the pattern (an+b) where “a” is the number you gave it, “n” is a set of all integers, and “b” is the offset that I’ve seen omitted (which defaults to zero). This is what happens:
As you can see:
:nth-child(1n)
matches every single column. And why wouldn’t it? The set described as (1n+0) where n is a set of all integers results in […0, 1, 2, 3, 4, 5, 6, 7, …]. In other words, this selector doesn’t actually do anything, you could stick with the element or class selector.- Similarly,
:nth-child(2n)
matches every other column because the set described as (2n+0) is […0, 2, 4, 6, 8, …] - 4n and 5n look safe, but what if there’s a second row?
TL;DR: The important take-away here is that the repetition is the a in an+b
. So a is the total number of columns in your layout, not the “column number” you’re trying to address. The column number is the “+b”, The Offset!
What you want is this:
:nth-child(6n+1)
matches the first column (the first, seventh, thirteenth, nineteenth, … elements):nth-child(6n+6)
, which can be written simply as:nth-child(6n)
, matches the last column (the sixth, twelfth, eighteenth, … elements)- Why can the “+6” column number (the offset) be omitted? The set of (6n+6) is […-6, 0 6, 12, 18, 24, 30…] and the set of (6n) is […-12, -6, -, 6, 12, 18, 24…]; they are the same.
- If dropping the offset might be confusing when re-reading code, then leave it! It does no harm, and might even be a good idea to retain.
And this stacks vertically as expected:
But the way I had it (usually) worked! What’s wrong with that? Two reasons: it doesn’t always work, and pride. 😉
Look at it this way:
By selecting :nth-child(1n)
, the middle column is selected, too!
- The reason this may go unnoticed is that the
:nth-child(2n)
is likely written after the 1n rule, with the same selector specificity, so it overrides the first.
Two problems arise when selecting :nth-child(2n)
:
- The first column of the second row is also selected. Why? It’s the fourth element, which is in the set defined as (2n).
- That second column on the fourth row is not selected. Why? It’s the eleventh element, which is not in the set defined as (2n).
- Worse, with an odd number of columns, even/odd flips on each row! Count it out:
- You’d run into the same changing “rhythm” with other numbers of columns or a different incorrect interval. (This might be your intention, if you were making a pattern, and it’s cool that CSS lets us do that so easily, but ya gotta know the rule before you break it.)
Why am I pestering you with this?
- Even if the site renders correctly now, imprecise code is difficult to maintain.
- If someone is inspecting your code, I want them to see excellent examples of your work.
:nth-child()
is one of my favorite selectors because of its power, which I encourage to be used for carefully constructed good.- Because if you don’t, I’ll make you use dastardly class names like
.three-column-layout--column-first
which seem to be all the rage right now.- Some folks out there think that this is the right way to go. The only convincing argument I see in favor of this approach is that it allows developers to avoid learning.
- Using intelligent selectors is far more graceful.
Does it matter if only one row is used in the layout?
Yes. That :nth-child(1n)
selects every column and the :nth-child(2n)
selects every even column, overriding the first rule. That’s inefficient and makes debugging and careful overrides that much harder.
And what about :nth-child()
vs. :nth-of-type()
?
element:nth-of-type()
uses the same math aselement:nth-child()
, but anything that isn’telement
will be skipped in the counting in addition to not being selected. In other words, when counting the addresses of elements to selectdiv:nth-of-type()
, aspan
would not be counted as an element in the set.- I frequently see
nth-of-type()
whennth-child()
would have been sufficient. As such,nth-child()
is probably the safer option (unless you need type specificity in counting) because it will make erroneous or disorganized HTML output more obvious. - Bottom line: specificity and consistency. Using the simplest selector possible for the task at hand is a CSS best practice. Straying from consistency is my biggest pet peeve.
Additional Resources
- :nth-child on MDN
- :nth Tester on CSS-Tricks – This right here, for visual or kinetic learners is a lifesaver, and is how I learned this concept.
Making the web a better place to teach, learn, and advocate starts here...
When you subscribe to our newsletter!