Perfect Three Column Grid Layout with CSS and nth-child Selectors

Want to watch a video of this post instead of reading it?

So you want perfectly even columns, three of them in a row, and you don’t want them to break if one div is longer than those that come after it?

Well here you go:


<article class="blog-entry">
<a href="/somewhere"><img src="image.jpg" alt="my alt text"></a>
<h2 class="entry-title">My Title</h2>
<p>Here is my text. <a href="/somewhere">Read more.</a></p>

That’s a pretty typical layout for a blog post entry as displayed on a listing page, such as a WordPress’ home page or category page. Take that and repeat it as many times as you’d like, one for each of the entries you’d have on such a blog page.


I’ll explain how this all works below, but if you just want to get right to it, here’s the CSS:

.blog-entry {float:left; width:33.33333%; margin-bottom:1em;}
.blog-entry:nth-of-type(3n+1) {padding-right:1%;}
.blog-entry:nth-of-type(3n+2) {padding:0 .5%;}
.blog-entry:nth-of-type(3n+3) {padding-left:1%;}
.blog-entry img {width:100%; height:auto; margin:0;}
.blog-entry:nth-of-type(3n+4) {clear:left;}

That gives you three panels, each one exactly the same width, and with even spacing between the first and second, and second and third panel. We end up with something like this:

screenshot showing everything laid out as per this tutorial
The final result.

How This CSS Works

Let’s break apart that CSS and discuss what would happen if we don’t use every single piece of it.

First we have this:

.blog-entry {float:left; width:33.33333%; margin-bottom:1em;}

That’s straightforward enough. We’re floating everything to the left, and making it all (as close to) 1/3rd of the maximum width available, depending on the user’s browser or any containing elements. But then comes the trick of adding padding, i.e. a little white space, between the panels but keeping them all the same width inside. You can’t just add padding-right:2em; or something like that, because then the last panel in each row will get padded, too, which we don’t want, because that’s not filling up the entire width of the containing element.

Thus we do this:

.blog-entry:nth-of-type(3n+1) {padding-right:1%;}
.blog-entry:nth-of-type(3n+2) {padding:0 .5%;}
.blog-entry:nth-of-type(3n+3) {padding-left:1%;}

Those :nth-of-type() entries are pseudo-selectors and they’re simultaneously awesome and complex to understand. In this case, we’re saying the following three things:

“Take every 3rd element, starting from the first (so 1, 4, 7, 10 and so on) and give it a padding of 1% on the right side.”

Then, “Take every 3rd element, starting from the second (so 2, 5, 8, 11 and so on) and give it .5% padding on each side.” Two half percentages equals one percent, and when combined with the other padding declarations, gives us a total padding of 1.5% on each side of all of the middle panels.

Finally, we say “Take every 3rd element, starting from the 3rd element, and pad it 1% on the left side.”

Note! All of this depends on you using the box-sizing:border-box; methodology, so that our inner padding isn’t adding to the total size of the panels, but instead taking away. You should be doing this anyway, if you want to be building responsive sites without killing yourself with frustration, but if you’re not aware of this technique, check out the CSS tricks article all about it.

Chris at CSS tricks also has a great tool to help simplify the process of wrapping your brain around these nth-child() selectors.

Moving on…

.blog-entry img {width:100%; height:auto; margin:0;}

All this does is makes sure our images are 100% of the width of their containing panel, which makes everything look nice but this is not vital to the perfect three column grid setup we’re working on here.

Now for a really important part of the code:

.blog-entry:nth-of-type(3n+4) {clear:left;}

In my example photo above, every panel has the same content. The headlines are all one liners, the excerpts three lines, and the images are all the same height. What if they weren’t? Take a look at this:

layout screwed up due to not using this latest piece of CSS
The second panel has a greater height than the other boxes, and so the fourth element can’t move below it, which screws up the entire page.

Not only does the extra height prevent our fourth panel from falling into place, it moves every panel afterwards into a different position in the grid than our CSS can account for. To further illustrate what’s happening:

the taller panel highlighted to show how it is bumping the fourth one back

The highlighted areas indicate the different parts of the second panel. The blue is the actual content area. The light green is the padding, and the orange is the bottom margin we’d added to each panel. See how the fourth panel is slightly above the bottom of where the bottom margin on the second element is? That’s preventing it from sliding all the way over to the left into it’s desired position.

Thus, we use this:

.blog-entry:nth-of-type(3n+4) {clear:left;}

That code says “Take every 3rd element, beginning from the fourth one, and make sure it clears everything.”

There you have it!

If you haven’t figured it out on your own yet, you can also simplify this greatly with:

.blog-entry {float:left; width:32%; margin:0 2% 1em 0;}
.blog-entry:nth-of-type(3n+3) {margin-right:0;}
.blog-entry img {width:100%; height:auto; margin:0;}
.blog-entry:nth-of-type(3n+4) {clear:left;}

So each panel is 32% wide, and the first two have a 2% margin after them. That is: 32 + 2 + 32 + 2 + 32 = 100. But where is the nth-of-type() fun in that?

Extra! The folks over a Webucator put this post together as a video you can watch, if you’re not the reading type!

Up Next: If a Minified Stylesheet Exists, Use It!