Perfectly justified CSS grid technique using inline-block

(TL;DR – see this test page with the final result of what I’m gonna talk about.)

You know these simple horizontal menu’s? Just take some ul > li > a, set the li to either float: left or display: inline-block and it’s done. I’m probably not telling you anything new there.

Earlier this year I was tinkering around with such a menu. When you work with a specifically designed navigation, which is almost always the case, you have a graphical style you want to go for. And, if you’re somewhat like me, you want it to be as perfect as can be. Let me pose the problem here. Sometimes, you need a menu that is perfectly aligned to both the left and the right. To illustrate this I present this highly scientific visual example of what I mean:

Justified CSS grid example

Now, I’m sure there will be a fancy way the new kids do this with CSS3 flexbox layouts, but I’m solving this old style here. Partly to support older browsers, and partly because I know too little about flexbox. The technique I use to solve this can also be called a “hack”, as it involves an unintended use of text-align: justify.

Justify and display: inline-block

Let’s go over some basics quickly. Block level elements honor “physical” properties like width and height and are characterized by their blocky appearance (i.e. they have auto 100% width and begin a new line). Inline elements go with the flow and follow their place in lines of text. Inline-block elements have something of both worlds: they can be altered in their physical properties, but also follow text flow in their position.

“Duh. Why is this important?”

It means we can (ab)use inline-block elements to form a horizontal navigation bar like the one above. To do this we use the text-align property of justify. Using justification on a normal text means it is aligned to the left as well as to the right, like so:

And I’ve found that no matter where I am, or who I’m talking to, there’s a common theme that emerges. Some will see this as an attempt to justify or excuse comments that are simply inexcusable. They are anxious about their futures, and feel their dreams slipping away; in an era of stagnant wages and global competition, opportunity comes to be seen as a zero sum game, in which your dreams come at my expense.

Source: obamaipsum.com

But, more importantly, the words of one sentence are spread out evenly over the with of the containing element. So, what if these words were elements? Can we do that? Yes, with inline-block, we can! 

First attempts

So let’s set a number of elements to display: inline-block and throw them in a container with text-align: justify on it:

This can be anything.
This can be anything.
This can be anything.
This can be anything.

.wrapper{
  width: 550px;
  text-align: justify;
  background: firebrick;
}

.wrapper div{
  background: white;
  display: inline-block;
}

This results into:

Hmmm, not quite what we want, is it? Two problems: (1) the “words” (our divs) aren’t spread equally over the complete sentence, and (2) there is annoying white space we don’t want. Let’s deal with that later. The words don’t spread evenly because the sentence is too short. There is nothing that kicks the sentence into “spread mode” because its length is shorter than that of its wrapper. Add more items (words), and it will happen: 

This can be anything.
This can be anything.
This can be anything.
This can be anything.
This can be anything.
This can be anything.

With the same CSS, the result is:

Cool, right?! The top row of elements is automatically aligned left and right to the parent div. But it does look totally ugly with the extra elements.

So what if we could add the extra elements in another way that doesn’t show an extra line? We need a method to dynamically add an element inside the wrapper, after the content, that overflows the “sentence”… let’s do some pseudo-element magic!

Using :after to force a line-break

Here, the css :after pseudo-element is a perfect solution. It resides inside the assigned element, but comes after the content. The :after element needs to be inline-block as well to be able to overflow the sentence. Next to this, we assign a width: 100% to it to make sure it will always be triggering the line-break: 

.wrapper{
  width: 550px;
  text-align: justify;
  background: firebrick;
}

.wrapper div{
  background: white;
  display: inline-block;
}

.wrapper:after{
  content: "";
  width: 100%;
  display: inline-block;
}

The result:

Sweet, now we almost have what we want. Only the problem of superfluous white-space remains.

Removing white-space

Many other smart developers have already taken their different takes on this, so I just searched for the best solution:

One way to do it is to chain the elements right after one another. No space in HTML. Keeping an eye on the restrictions and auto-formatting behavior of many content management and online publishing systems, this seemed like an impractical solution to me. The same goes for adding empty HTML comments in between the tags.

Another way is to leave out the end tags. Yes, you can do that in HTML5. Unfortunately, this breaks the whole construction in my experiments. It doesn’t work anymore when the end tags are removed.

This leaves us with one of the nicest and cleanest options available: setting the font-size of the wrapper element to 0. This works perfectly with our solutions, and it’s CSS-only. This actually breaks up the whole thing in IE. Hmpf. As I have discovered, a better solution is to keep the spacing in between inline-block elements. This way, inline-block elements don’t “stick” to each other. The only thing we have to do now is to get rid of the space the :after element is taking up. The good news: font-size: 0 on the wrapper element takes care of this. The bad news: IE breaks. Alas, the only “solution” I have found so far is to use an IE-specific hack to reset the font-size again. IE users will be left with a very small extra space at the bottom of the wrapper. So be it, for now.

Without any unwanted white-space but including IE hacks (a small inline-block fix for IE6/7 is also implemented here using zoom: 1; and *display: inline), the final solution looks like this:

.wrapper{
  width: 550px;
  text-align: justify;
  background: firebrick;
  font-size: 0;
  font-size: 12px\9; /* IE6-9 only hack */
}

.wrapper div{
  background: white;
  display: inline-block;
  font-size: 12px;

  zoom: 1;
  *display: inline;
}

.wrapper:after{
  content: "";
  width: 100%;
  display: inline-block;

  zoom: 1;
  *display: inline;
}

Result:

And that’s it! With such relatively simple CSS, you can achieve a very flexible grid-like layout. The nice this about this is that the widths of the elements can also be percentages. Try scaling your browser window on the test page and see the automatic alignment magic happen.

That small test page lets you try out different numbers of elements and different widths. Useful for testing and debugging.

So, what’s your take on this? Have you used this technique before? Considering browser support, how would you handle it? Do you know of any way to make the :after element take up no space across all browsers without using font-size: 0? Let me know in the comments!

Discussion

  1. Marcus Stade February 12, 2013

    Since you’re the one setting the font-size (and thus has all the info) you should be able to set a negative margin on the children of the container. So .wrapper gets font-size: 1px; and .wrapper div gets margin-right: -1px.

  2. Jelmer March 24, 2013

    Hmmm good idea, I would have to test it though because it seems like (1) there is more space because of font-size: 1px than just the margin at the right, and (2) not sure what IE will then make of it.

  3. BJ June 9, 2013

    Care to give me a hand with this?

    Use the exact CSS above.

    Now pretend you populate the wrapper with child divs via javascript loop with this pseudo code:

    Loop for X:
    var wrapper = getElementById(‘wrapper’);
    var newchild = createElement(‘div’);
    newchild.id= “child” + x;
    wrapper.appendChild(newchild);

    The resulting HTML from javascript looks like:

    contentcontentcontentcontent

    This won’t style correctly. The content is correct, but the HTML needs to look like:

    content
    content
    content
    content

    Any idea how to nail this in Javascript?

  4. Siddharth Menon June 21, 2013

    Hey, this is a great solution. However I am not quite happy with Justify, there is no definite spacing between blocks.

    So if I have 5 blocks, it should be spaced out with a gutter space of say 20px. That way I can keep consistency when defining vertical spacing between the blocks.

    This will be a great solution when dealing with different screen sizes. For smaller screens blocks per line would be 3-4 and it should look like perfect grid.

    IMO, if we can get to collapse the wrapper div to number of blocks some how it will solve all the white spacing issues.

    Thanks again for a nice post.

  5. Jelmer June 22, 2013

    You are welcome! This hack was/is meant mainly for responsive purposes when you don’t want a specific gutter width. Collapsing the parent would possibly solve our problem, but I think that’s impossible to achieve here because we need the inline-block elements.

  6. nicmare July 8, 2013

    final result does not work in opera! the items are floating over the edge of the wrapper div. but result with white space works best in all browser (if you can live with white space).

  7. Paul July 22, 2013

    Hi,

    The whitespace bug can be cured easily by adding display:table and with:100% to the parent and avoids using font-size:0 which kills inheritance. Older browsers can use negative word spacing on the parent to kill the gap and then reset it to zero on the child. That will allow support right back to IE6. The full details can be found in a quiz I set many years ago:

    http://www.sitepoint.com/forums/showthread.php?709009-CSS-Test-Your-CSS-Skills-Number-35-inline-block&p=4724244#post4724244

    (We also solved the justified menu about 5 years ago in a similar quiz ;))

    Note that your after rule contains two useless rules:

    .wrapper:after{
    content: “”;
    width: 100%;
    display: inline-block;
    zoom: 1;
    *display: inline;
    }

    IE7 and under don’t understand :after so the zoom:1.0 is wasted as Ie8+ don’t have haslayout triggers as such. The *display:inline is also a rule only understood by IE7 and under and of course will never be seen because they don’t understand :after. :) For ie7 and under support you would need to add an extra element to use manually (or use expressions to mimic :after)

    Hope that helps.

  8. Comments are closed.