Preventing Orphans in Flexbox
Using :nth-last-child
to avoid a standalone element.
Extra
Preventing orphan elements with pure CSS—more on that in a bit—might not be something you’ll actually want to pursue, but it gives us a good reason to learn more about the :nth-last-child
selector.
Some groundwork
While :nth-child(3)
can be used to select the third child inside a container, :nth-last-child(3)
selects the antepenultimate child, because we start with the last child and count backwards. To select not just one but every third child, we use :nth-child(3n)
or :nth-last-child(3n)
.
We can add positive or negative offsets. E.g., to select each child that follows immediately after every third child, we put 3n + 1
inside the parentheses.
Let’s keep the focus on 3n + 1, as this is where in the depicted example both nth-child and nth-last-child target the same elements. Which is simply due to the number of elements we have in total. If we add children to the container, things run out of sync. (In this particular case, only if we add one or two elements. Adding three elements brings things back in sync, courtesy of the 3n part in our selector. And so on.)
Now if—for some reason—we only want to target children where both nth-child and nth-last-child apply, we simply combine both selectors.
Do you see where this is going? If not, maybe it’s time to wrap our children onto multiple lines.
Detecting a single element in the last row
The Flexible Box Module, usually referred to as flexbox, was designed as a one-dimensional layout model,
is what MDN has to say about flexbox. While there are key differences to CSS Grid Layout, which gives us two dimensions right out of the gate, flex items may also wrap, thanks to flex-wrap: wrap
.
Let’s make the container wide enough to fit three (yes, 3n correlation) children. We wrap after every third child, which means 3n + 1 children will be first in their row.
In this flex-wrap scenario, only the container on the left happens to produce an orphan in the last row, which is also the one where nth-child and nth-last-child meet. For our CSS to apply only in cases where the total amount of children is 3n + 1, we once again combine the two selectors.
Doing so, we’ve selected all children that are first in a row, in other words the ones in the first column. And we’ve limited our selection to containers that produce an orphan child in the last row, while containers with a children count other than 3n + 1 are unaffected.
As we are not interested in these individual first-column children, we now reduce this even further, and only select the first child, by adding :first-child
. As first-child also takes care of what we needed nth-child for, we may conveniently reduce it to :first-child:nth-last-child(3n + 1)
.
Having selected the initial child in orphan-producing containers, we can now either do something with that child directly, or select any child that follows using the general sibling combinator.
Here we are, ready to prevent an orphan element in the last row. The idea is to create an empty slot elsewhere, so that a second child gets pushed down into the last row. We choose a child and add a margin (left or right) to it, with the margin value being the element width. The use of margins is the reason why this will only work in a flexbox container, but not in grid. A margin on the left will shift the chosen child and all that follow. A margin on the right will only shift those after the chosen child. Which child should be the chosen one? That depends on the visual effect you want to achieve.
Interactive playground
This solution will work for any number of rows and columns. For fewer or more columns, simply adjust the container width and replace 3n with your desired value. To get an even better understanding, head over to my interactive version on CodePen, where you can play around with different settings.
Postscript: A word on gap
Adding a gap between the children is possible, but at the time of writing, setting a gap
on flex containers is not supported in Safari. This can be achieved by setting a margin on each item, and negative margins on the container. No matter which one you add, gap
or margin
, the gap has to be taken into account when calculating the width of the container, and offsetting the items.