May 23, 2010 6

Cross Browser Inline-Block

By in css

How many times have you used float:left to make a bunch of elements align horizontally? Usually this technique is used on lists (of both the ul and ol persuasion) and generally works just fine under a few preconditions. The first precondition is that you are comfortable using some form of a clear-fix hack. Either you apply overflow:hidden to the container, or you add a meaningless empty div with clear:both, or you apply some magic with the :after pseudo-class. While this isn’t a deal breaker, it is annoying.

The second precondition is that all floated elements are of the same (fixed) height. Of course, your original mock-ups with dummy data and lorem ipsum text all use the same element just repeated across the page, right? And then the client provides actual content (hopefully), and you find that some items have a really long title, or a super-tall product image. And what happens? The floats ‘snag’ items and you end up with this:

float see fiddle

If the previous screenshot is what you going for, or both previous preconditions hold in your case, you may stop reading right now. Otherwise, read on and you’ll learn how to create this instead:

final see fiddle

First, I must give credit where credit is due. The technique I’m about to describe (and improve upon) was covered by Ryan Doherty over at the Mozilla WebDev blog. I suggest you read his post first because I’m going to breeze through his foundation and jump into my improvements.

Foundation

Here is the HTML will be working with:

<ul>
    <li>
        <h3>Product 1</h3>
        <p>Some product description.</p>
    </li>
    <li>
        <h3>Product 2</h3>
        <p>Some product description.</p>
    </li>
    <li>
        <h3>Product 3</h3>
        <p>Some really, super, long product description.</p>
    </li>
    ...
</ul>

And some aesthetic styles not relevant to the inline-block layout:

ul {
    list-style:none;
    width:440px;
}
li {
    border:1px solid #888;
    -moz-border-radius:3px;
    width:75px;
    text-align:center;
    margin:5px;
}
h3 {
    font-size:14px;
    font-weight:bold;
    margin:.25em;
}
p {
    margin:.25em;
}

Compliant Browsers

The first step is to try an use the proper CSS property: display:inline-block. This property gives an inline element, block-like properties. Such as the ability to have width and margin. With just this property, we have support in Firefox 3+, Safari 3+, Chrome 1+, Opera 9+, and IE8+. Not too shabby.

li {
    display:inline-block;
}

valign

Fixing the Rest: Firefox 2

Aside from IE 6 and 7, only Firefox 2 fails to support inline-block. To get support in Firefox 2, simply add the mozilla-specific property display:-moz-inline-stack prior to the display:inline-block declaration. Non-Gecko browsers will ignore the -moz rule. Firefox 3+ supports inline-block so it will override the -moz-inline-stack rule. Using the -moz-inline-stack rule for Firefox 2 also necessitates a div be wrapped around the inline-block element’s contents. Given Firefox 2’s latest market share numbers (most give it under 1% overall), I would generally concede that messing with -moz-inline-stack and an inner-wrapping div is unnecessary. For this reason, I have removed the -moz-inline-stack rule from my Inline-Block gist on GitHub, however, you can see it in action at jsfiddle. Feel free to add it back in yourself if Firefox 2 support is necessary.

Fixing the Rest: IE 6/7

IE 6 and 7 both support inline-block natively but with a caveat; they only support it on elements that are inherently inline. Thus, block elements like div and list-item elements like li won’t apply inline-block. However, IE 6/7 has the concept of ‘layout’. (see On Having Layout). IE treats elements with layout triggered exactly the way inline-block elements are supposed to work; that is, block-level elements that are displayed inline. So for IE6/7 we reset the display to inline, and trigger hasLayout with zoom:1.

* Note: You can apply the IE 6/7 rules in any manner you wish. Conditional Comments paired with IE-only stylesheets is generally the preferred method. However, I have gone with the *hack in this case to make these utility classes copy/paste-able.

li {
    display:inline-block;
    *zoom:1;
    *display:inline;
}

The final touch is to set vertical-align:top to make the boxes line up across the top:

li {
    display:inline-block;
    *zoom:1;
    *display:inline;
    vertical-align:top;
}

final

Room for Improvement

So now we have our elements aligned horizontally without using floats. We have one last problem which is where I will improve on Ryan’s method. In the screenshot below I have changed the li margin to margin:5px 0; We would expect the left and right borders of each box to touch, no?

whitespace

The problem is due to the fact that white-space surrounding inline elements is displayed. Of course, this makes perfect sense. Imagine if the white-space between words in a sentence weren’t displayed! The trick is to take advantage of letter-spacing and word-spacing to counter the white-space between our inline-block elements. (This is unnecessary in IE6/7 which already ignores the white-space between boxes because the elements have hasLayout triggered and are not technically inline-block.)

Six of One…

My first attempt was to apply a negative word-spacing to the container. Word-spacing is inherited, so we must reset it to normal on our inline-block elements themselves so as to not affect their children. With word-spacing set to -1em, we have eliminated the offending white-space in Firefox and Opera.

nowhitespace see fiddle

…Half a Dozen of the Other

In order to fix WebKit (Safari, Chrome) we must also apply negative letter-spacing. Interestingly, applying only letter-spacing actually fixes both WebKit and Firefox which means that either letter-spacing or word-spacing will work for Firefox. However, in order to appease Opera, we will apply both. see fiddle

All Together Now

I have pared down the necessary rules to a set of utility classes named ib-container and ib-block, as seen below. You can also find them in a gist on GitHub and as a fiddle on jsFiddle.

.ib-block {
    vertical-align:top;
    display:inline-block;
    *zoom:1; /* IE6/7 */
    *display:inline; /* IE6/7 */
}
.ib-container {
    letter-spacing:-.25em;
    word-spacing:-1em;
}
.ib-container .ib-block {
    letter-spacing:normal;
    word-spacing:normal;
}

Other Variations

They can be used on list items as seen in this example or any other set of sibling elements:

<div class="ib-container">
    <div class="ib-block">…</div>
    <div class="ib-block">…</div>
    <div class="ib-block">…</div>
</div>

They can be used without the ib-container class if the white-space between ib-block elements is not an issue for you:

<ul>
    <li class="ib-block">…</li>
    <li class="ib-block">…</li>
    <li class="ib-block">…</li>
</ul>

They can be nested so an ib-block element becomes an ib-container for other ib-block elements:

<ul class="ib-container">
    <li class="ib-block ib-container">
        <div class="ib-block">…</div>
        <div class="ib-block">…</div>
    </li>
    <li class="ib-block ib-container">
        <div class="ib-block">…</div>
        <div class="ib-block">…</div>
    </li>
    <li class="ib-block">…</li>
</ul>
 
 

Tags: , ,

6 Responses to “Cross Browser Inline-Block”

  1. Chris Poteet says:

    Interesting choice to use a list to structure content that contains paragraphs and headers. I would’ve gravitated to just the DIV. I wonder if the semantic value of the content you have above is a list of items?

    PS. Subscribed to blog.

  2. jasonkarns says:

    @chris I agree completely. The semantic value of my example markup is suspect. If headings and paragraphs are indeed all that’s contained in each ib-block item, then wrapping divs should be used (or HTML5 elements article/section) instead of ul/li. I began my example planning on using a list of products (title, description, image, price, rating, etc) and then stripped out all the extra content for brevity. At that point I forgot to revert the markup to divs.

  3. camslice says:

    Hey, looks like you were on same track as me with this, funny that I only stumbled across your page just now. I’ve created an entire CSS layout system using this technique:
    http://stacklayout.com

    The problem with using word-spacing for all browsers is if you don’t specify the width of a component, then the word-spacing property actually changes the position of that component in addition to removing the whitespace. Check out the purple components on this test page:
    http://stacklayout.com/test.html

    My solution to the inaccurate rendering of white-space across browsers was to use a ‘control font’ – Courier New – which is a fixed width font. So my letter/word-spacing becomes -0.65em

    Anyhoo, have a look around StackLayout and let me know what you think :)

    camslice
    tweet @camslizzle

  4. Sean says:

    Great article. Will have to look into this for some of my sites.

    Out of curiosity, why not use negative margins to close the gap instead of letter spacing? Removing the letter spacing and changing the margin on the li to 5px -2px worked in everything but IE7, where the left and the right needed to be -1px. I am betting that inconsistency could be fixed with a CSS reset/rebuild.

    See http://jsfiddle.net/4pvyg/51/

  5. jasonkarns says:

    The reason we went with letter-spacing is because of the nature of the white-space between elements. The spacing between elements is not a fixed size. Since the gap is an actual space, the width of the gap is dependent on the font type (monospace vs not) and the font-size. Using a negative margin will work when the text is sized normally (in the 12-14px range). However, if your design calls for larger text, or the user has a higher font-size setting, then the negative margin will not be enough to close the gap. The reverse holds true if the font-size is reduced (the negative margin would over-compensate and the elements would overlap).

    However, it’s possible that changing the negative margin to use an em-size rather than px would work as well.

  6. I have entered this code however the non-mozilla browsers answer display:-moz-inline-stack; and because they don’t know what it is, inline-block fails to show! Even when I add !important deceleration to display:inline-block; it still doesn’t work! Any explanation?

Leave a Reply

You must be logged in to post a comment.