grack.com

If you find this interesting, read more about CSS Selectors Level 4 which will offer you even more tools for stylesheet development.

CSS3’s :nth-child and :nth-last-child selectors are powerful: not only can they replace :first-child and :last-child, but they can style more complex patterns like the first (or all but the first) three children, every fourth child, or combinations of the pattern “a*n+b”.

But did you know that you can do more interesting things with the selectors? For example, you can style the third element, but only when it’s one of five child (the virtual :nth-of-m-child selector we’ll discuss below). Or that you can style all of the children of an element with m children (another virtual selector we’ll call :family-of-m).

You might ask why we’d want to do this – the particular use case that I had in mind was a Javascript-free, automatically-sizing image gallery that I could toss in the stylesheet for my Jekyll-based site and have it “just work” regardless of the number of images I threw at it.

Here’s an example of what it looks like (click here to view it full-page). Note how the images automatically size to a regular grid without having to use Javascript:

:nth-of-m-child

Thanks to xantys on HN for pointing me at much earlier work in this area here and here.

The first virtual selector we’ll construct is something I call :nth-of-m-child. This will allow us to style the nth child when it’s one of m children, and will be our building block for further work.

So, how do we get this selector? Easy: we combine :nth-child and :nth-last-child on a single element to select when the element to be styled is in the correct position from the start and end of the list of children. For example, we can style the third element, if and only if it’s one of five children:

span:nth-child(3):nth-last-child(3) { ... }

Breaking that down: that’s the third child and the third-last child. Given ‘n’ and ‘m’, the general formula is :nth-child(n):nth-last-child(m+1-n).

Here’s an example of this in action (click here to view it full-page):

:family-of-m

Now that we have the ability to style n-of-m, we can expand that out to style all of the children where there are m of them directly underneath a parent node. The secret to this is using the CSS3 ~ non-adjacent sibling selector which will continue matching elements across siblings. For example, the selector:

img ~ span { ... }

will match a <span> if and only if one of its previous siblings was an <img> element regardless of the number of siblings between them. We’ll combine this selector with our :nth-of-m-child pattern like so:

span:nth-child(1):nth-last-child(5) ~ span { ... }

This pattern will match any adjacent siblings to the first element of five, ie: the second through the fifth of five. We can make it match the entire row by using a comma to match the first element as well.

span:nth-child(1):nth-last-child(5), 
	span:nth-child(1):nth-last-child(5) ~ span { ... }

Here’s an example of this in action (click here to view it full-page):

Advanced techniques

Alright, now we have the tools in place for us to style images as seen in the example.

The first grouping of one through four are simple applications of the technique. When we get to five we want to start using a pattern where the first line or two are larger pairs and the remainder of the images are in triplets. This pattern should repeat regardless of the number of images.

Here’s a commented example. Note that this might potentially be simplified using flexbox and wrapping, but that’s an exercise for the reader.

Let’s start with fifth, eighth, eleventh and the remainder of this pattern. Instead of using the :nth-child(1):nth-last-child(...) as we did before, we’ll use :nth-child(1):nth-last-child(3n+5). This will match the first element of a grouping of 5, 8, 11, …:

/* First two are half-sized (99% / 2) */
img:first-child:nth-last-child(3n+5) ~ img, img:first-child:nth-last-child(3n+5) {
	max-width: 49.5%;
	margin-right: 1%;
}

/* Last n - 2 are (98% / 3) */
img:first-child:nth-last-child(3n+5) + img ~ img {
	max-width: 32.6%;
	margin-right: 1%;
}

/* But second, fifth, eighth, ... have no right margin */
img:first-child:nth-last-child(3n+5) ~ img:nth-child(3n+2) {
	margin-right: 0;
}

Six, nine, and twelve are much simpler.

/* Six, nine, twelve, ... are all (98% / 3) */
img:first-child:nth-last-child(3n+6) ~ img, img:first-child:nth-last-child(3n+6) {
	max-width: 32.6%;
	margin-right: 1%;
}

/* Every third one of these has no right margin. */
img:first-child:nth-last-child(3n+6) ~ img:nth-child(3n) {
	margin-right: 0;
}

Seven, ten, thirteen, and the remainder of the pattern are similar to the 5/8/11 pattern:

/* First four are half-sized (99% / 2) */
img:first-child:nth-last-child(3n+7) ~ img, img:first-child:nth-last-child(3n+7) {
	max-width: 49.5%;
	margin-right: 1%;
}

/* Last n - 4 are (98% / 3) */
img:first-child:nth-last-child(3n+7) + img + img + img ~ img {
	max-width: 32.6%;
	margin-right: 1%;
}

/* The second and fourth, seventh, tenth, ... have no right margin */
img:first-child:nth-last-child(3n+7) + img, 
	img:first-child:nth-last-child(3n+7) ~ img:nth-child(3n+4) {
	margin-right: 0;
	outline: 1px solid red;
}

Conclusions, notes, and further work

We can do some really interesting things making using of :nth-child and sibling selectors. The techniques in this post will also work for the related selectors :nth-of-type and :nth-last-of-type.

Browser performance doesn’t appear to be noticeably affected on mobile or desktop using this technique. If you plan on scaling this up it’s obviously something you’ll want to test.

If you were to combine this with flexbox and flex-wrap you might be able to simplify the example even further and might be able to handle images of different sizes more elegantly.

You also can also use this to create other interesting patterns, like matching only when the total number of children is even or odd, or other factor-based qualifiers.

I’d love to hear of any ideas or improvements you might have. Play around with the JSFiddle here.

Thanks to Webucator for creating a video for this post as part of their CSS3 training course series:

Comments? Follow me on Twitter @mmastrac and let me know.

Read full post