Flexbox Grid Finesse
(This post was originally published on Medium, put has a permanent home here.)
Flexbox—not to be confused with Sex Box, the British TV show wherein Mariella Frostrup interviews people who’ve just had sex in a box—is the CSS layout toolkit de rigueur. Of all the celebrated features of Flexbox, it is the light work it makes of producing wrappable grids, tolerant of dynamic content, that I think’s integral.
In this article, I’ll cover a few techniques to exploit Flexbox’s internal algorithms and design finessed grids intended for changing quantities and dimensions of content.
Basic wrapping
.parent {
display: flex;
flex-flow: row wrap;
}
.child {
flex: 1 0 25%;
}
I’ve made the .parent
a flex container and used the flex-flow
shorthand to set the flex-direction
to row
(i.e. following the horizontal axis) and enabled wrapping. Wrapping operates according to the flex-basis
set on children; in this case 25%
.
100 divided by 25 is 4, meaning what we have here is a 4 column grid. Simple stuff, I know. With wrapping on, a new row is begun every time you exceed 4 additional children. The last row is always complete, either because the total is exactly divisible by 4, or because the left over children are “grown” to share a row’s width. The “1” in flex: 1 0 25%
essentially means the ability for children to grow is set to on.
The upshot is that you can employ a grid which “tidies” itself, distributing children automatically. This is powerful stuff. I can be assured that no matter the number of children currently included, my layout will be acceptable.
Element queries
I’m not the first (I think Zoe Gillenwater pointed it out) to notice that Flexbox can be made to employ something akin to “element queries”; changes in an element’s layout based on that element’s own dimensions. By incorporating a min-width
into the last example, I can trigger elements to grow at this “breakpoint”.
.parent {
display: flex;
flex-flow: row wrap;
}
.child {
flex: 1 0 25%;
min-width: 5em;
}
I have made my grid entirely responsive with just one additional declaration. The number of columns is predicated on the simple axiom that no one column (element) can be fewer than 5em
in width. Flexbox’s grow
takes care of expanding children that hit this minimum width, making sure they never get any narrower than this setting. Wrapping takes care of everything else.
Dealing with remainders of 1
If I have a 4 column grid with 8 children present then I add a child element, that element will constitute the whole of the bottom row. Ideally, I’d like to move things around so that this is not the case. By harnessing the ability of my elements to grow and incorporating nome nth-child
magic, I can essentially borrow elements from the penultimate row to distribute the elements towards the end of the grid more reasonably. I can also do this without negatively affecting other quantities of elements producing more than a single remainder or no remainder at all.
In the following example, I’ve adapted the first example in this article.
.parent {
display: flex;
flex-flow: row wrap;
}
.child {
flex: 1 0 25%;
}
.child:nth-last-child(2):nth-child(4n) {
min-width: 33%;
}
This selector expression in the last block targets any element that falls in the fourth and final column (4n
) which is simultaneously the penultimate element ”“ that is, the element before the long, single remainder. By bumping this element’s width to 33%
, it’s forced down into the final line, leaving the penultimate line with just three elements.
The result is the elimination of the single element and a dynamic grid that resolves into a line of 4, then 3, then 2 children when there is a single remainder. No matter how many child elements are present, the grid never ends with a single element row.
Try adding and removing items in this codePen demo and experimenting with different numbers of columns. The basic formula for the selector is .child:nth-last-child(2):nth-child([number of columns]n)
. The width you set has to be somewhere between the base width and the base width for the grid if it had one less column. So, if there are five columns, the width set in this override should be between 20% and 25%.
Controlled chaos
In the last example, I singled out a child element based on its index using the algebraic :nth-child
and nth-last-child
. Because of my Flexbox configuration’s insistence of filling the available space according to its element growth and wrapping features, I could do this safe in the knowledge that I would not produce an incomplete, gap-ridden grid.
Based on this principle, I can arbitrarily change the widths of any grid children I like. I can get quite expressive with this and build in some algorithmic asymmetry.
.child:nth-child(3n) {
width: 33.333%;
}
.child:nth-child(5n) {
width: 50%;
}
.child:nth-child(7n) {
width: 66.666%;
}
By using prime numbers (3, 5 and 7) to augment the child elements’ width at intervals, any perceived regularity in the layout can be easily diminished. However, the layout never breaks as such thanks to our go-to wrapping and growth settings. Be sure to try out the codePen demo for this one and experiment with different, superimposed nth-child
intervals.
Gutter tactics
(This is an edit to the original article after Stu Cox started a discussion about how one might deal with gutters.)
Gutters are, put simply, the gaps between grid children to space them. I’m not absolutely certain what the “flexy” way to add gutters to any of my described grids would be, but I am aware of one technique which would create gutters that do not break when wrapping produces remainders.
.parent {
display: flex;
flex-flow: row wrap;
margin-left: -0.5rem;
margin-right: -0.5rem;
}
.child {
flex: 1 0 25%;
box-sizing: border-box;
padding: 0 0.5rem 1rem;
}
It is important that I treat each item equally, rather than trying to anticipate things with nth children, because any of my items can potentially grow or wrap. Hence the padding: 0.5rem 1rem
on all .child
elements. This produces a 1rem
gutter around each child, no matter how they wrap or grow.
All that’s left is to compensate for the redundant 0.5rem space for left-most and right-most children, facilitated by the left and right negative margins on the parent (Stu arrived at this notion). Depending on the vertical rhythm of your page, you may want to remove the redundant bottom margin of the last child row too, with margin-bottom: -1rem on the parent too. There is a codePen to play with.
Conclusion
I hope that this short article has given you something to think about regarding the way Flexbox handles and tolerates dynamic content, allowing you to tersely code robust yet expressive layouts. With Flexbox, for the first time, we’re afforded something akin to true grid systems; grids which govern themselves, freeing us to focus on content creation and aesthetics.