blog
Evenly Spacing Columns with Only CSS
A JavaScript-free, cross-browser compatible (all major, and IE6+) solution to creating evenly spaced div elements in a fluid layout (reusable LESS mixins included)
Attachments: Demo.zip

As responsive, tile and grid-driven design continue to dominate the modern web, evenly spacing elements in a fluid layout with maximum cross-browser support is a must. JavaScript plug-ins are often used to implement this functionality, but they bring unnecessary overhead to the site, and cross-browser support and customization often pose implementation and maintenance challenges. In this post, I will show how this can be achieved using nothing but HTML and CSS, using a technique that is supported by IE6+ and all major browsers!

Markup

Let's start by evenly spacing four div elements. Here is our markup:

HTML
<div class="column-outer-row demo-outer-row">
    <div class="column">Some content...Column 1</div>
    <div class="column">Some content...Column 2</div>
    <div class="column">Some content...Column 3</div>
    <div class="column">Some content...Column 4</div>
</div>

Notice how I have assigned the classes .column-outer-row and .column to differentiate between the different elements. We will continue to use these going forward.

Styles

Now that we have our markup, it's time to have a look at our styles. As we go, I will show both the CSS and the LESS versions of the styles being discussed.

If you are following the LESS, note that I named the mixin .edc-outer-row, where edc stands for "Evenly Distributed Columns." Also note that we will be adding to the mixin as we go. To make it easier to read, I replace the existing rules with an ellipses (...) and only show the new rules that are being added. Don't worry, we will see the whole thing put together at the end.

Now that we have that covered, let's dive into our styles!

The Container

The following are the styles for the .column-outer-row element:

CSS
.column-outer-row {
    text-align: justify; /* REQUIRED for all */
    -ms-text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    font-size: 0; /* White-space fix */
}
/* Restore font-size for children */
.column-outer-row > * {
    font-size: 16px; 
}

LESS (Alternative)
/* Global variable for standard font-size */
@baseFontSize: 16px; 

/* Mixin */
.edc-outer-row(@fontSize: @baseFontSize) {
    text-align: justify; /* REQUIRED for all */
    -ms-text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    font-size: 0; /* White-space fix */

    /* Restore font-size for children */
    > * {
        font-size: @fontSize;
    }
}

/* Rules */
.column-outer-row {
    .edc-outer-row();
}

Details

Note: This element has been named the .column-outer-row as all of the columns contained within it must be constrained to fit on a single line (or row). It is the "outer" row because this technique creates a column-based grid, not a row-based one. In other words, each row is independent from one another and could have any number of columns. In other words, it is not like a table, in which your columns line up and are the same size but might be merged. However, if that is the functionality that you are looking for then what you want to do is create your columns and then create "inner rows" of a consistent height that have the same vertical alignment in all of your columns. This gets a little off topic, but feel free to reach out to me through my contact page if you have any questions.

Starting from the top, notice how the text-align property is used and set to justify. The justify value is used to tell the browser to evenly space the content over the entire width of the container. This property was designed to work with text, which is inline, and thus any inline element, including any inline-block element, will be effected. This is important, as we are going to make set our .column elements to display: inline-block;, as we will see later.

The one caveat to text-align: justify is that it only works when the content is long enough to normally cause a line-break. We work around this by using text-justify: distribute-all-lines and by using an invisible element with width: 100%, as we will see later. The distribute-all-lines value of the text-justify property tells the browser that the last line should be justified, regardless of length, though the default behavior is to leave it left-aligned.

Next, note that the setting font-size: 0; with the note that it is a white-space fix. Because the .column elements are to be displayed inline-block, the browser may interpret and render additional, undesirable white-space between the markup for our .column elements as well as within the container's tags. Note that this applies to any inline elements, and that in this case this additional white-space may result in extra margins after the last element. By setting the font-size to 0, we are effectively eliminating this white-space from the flow.

Lastly, we restore the font-size of the children. We can do this with individual styles rules for children or descendants of the .column-outer-row element, or we can restore them globally. I prefer to restore them globally, since we overrode them from this level, and to use a wildcard (*) selector to make the rule easy to override by more specific rules. Note that I assumed that the base size of the text would be 16px, but any value can be used here.

Note, I chose to include the style rule for restoring the font-size because, from a reusability standpoint, I do not want to include a rule that may cause changes in styles for the content within the columns. If my use-case requires that the font-size for the columns remain 0 then I would either create an additional, more specific style to override the "restoration rule," or I would pass 0 in as my value for the @fontSize parameter of my LESS mixin. This is just personal preference. The choice is yours.

The Columns

The following are the styles for the .column elements:

CSS
.column {
    display: inline-block; /* All major modern browsers */
    *display: inline; /* Browser hack for IE6/7 */
    zoom: 1; /* For IE 6/7 to display inline-block */
    vertical-align: top; /* Prevent staggering */
}

LESS
/* Mixin */
.edc-outer-row(@columnClass, @valign: top, @fontSize: @baseFontSize) {
    ...

    >.@{columnClass} { /* Make column class a parameter for greater reusability */       
        display: inline-block; /* All major modern browsers */        
        *display: inline; /* Browser hack for IE6/7 */        
        zoom: 1; /* For IE 6/7 to display inline-block */        
        vertical-align: @valign; /* Prevent staggering */
    }  
}

/* Rules */
.column-outer-row {
    .edc-row(column);
}

Details

The styles for the .column elements are fairly straight forward. First off, we plan to put more than one element on each line and want them to can be justified, thus we want them to be inline. We also want block-styling so that we can set dimensions. As such, we set the .column elements to display: inline-block;.

Unfortunately, IE6 and 7 do not allow elements that are not naturally inline to display as inline-block elements. A div is an example of an element that is not naturally displayed inline, whereas a span element is naturally displayed inline. In order to work around this, the next two property settings are used.

The *display: inline; setting is the first of the two used to get around IE6/7's inline-block support issue, and is fairly straight-forward. It sets our element to be inline, but has a prepended asterisk, *display... that ensures that the property will only be read by IE browsers that are IE 7 or earlier. This is known as a CSS hack. CSS hacks are safe and do are widely supported across browsers, however they are avoided so as to prevent overuse - one should not be using them unless it is a minor work-around for a legacy browser, as is the case here (read more about CSS hacks in the detailed reference, found at browserhacks.com).

The second property setting used to work around the IE6/7 inline-block support issue is the zoom: 1; setting. The zoom property is a Microsoft proprietary CSS property, and setting it to 1 has no visible affect on the element, except that it adds hasLayout to it (another Microsoft internal), that gives it block-styling. In IE 7 and earlier, in order to have block-styling, an element needed to have hasLayout, which was default on some elements, and could be received by others via the use of certain properties, like the zoom property (read more about hasLayout in the comphrenensive article found at satzansatz.de/cssd/onhavinglayout.html).

Now that we have all of our elements displaying as inline-block elements, we need to make sure that they don't stagger, that is we want to ensure that the elements are all aligned vertically with one another, and that none push up above the others. To do this, we use apply the vertical-align: top; property setting, which can can be modified to the desired alignment. For this reason, I added the @valign parameter to the LESS mixin, to be used to set the value of the vertical-align property.

After the Container

Our last style is for after the container, and is as follows:

.column-outer-row:after {
    content: ''; /* Ensure it displays */
    display: inline-block; /* Display on same line as columns */
    width: 100%; /* Ensure row's content is long enough to be justified */
    font-size: 0; /* Ensure (with line-height) does not take up visible space */
    line-height: 0; /* See font-size */
}

LESS (Alternative)
/* Mixin */
.edc-outer-row(@columnClass, @valign: top, @fontSize: @baseFontSize) {
    ...

    &:after {
        content: ''; /* Ensure it displays */
        display: inline-block; /* Display on same line as columns */
        width: 100%; /* Ensure row's content is long enough to be justified */
        font-size: 0; /* Ensure (with line-height) does not take up visible space */
        line-height: 0; /* See font-size */
    }  
}

/* Rules */
.column-outer-row {
    .edc-outer-row(column);
}

Details

Recall that used the IE text-justify: distribute-all-lines properties to work around the text-align: justify; setting requiring that the content be long enough to naturally cause a line-break. However, we now need to add support for this in all of the other browsers. As we are no longer worried about IE, we need only ensure that the above supports all browsers other than IE, which it does. What we are doing here is essentially creating a "fake" element that appears after the last .column element that is long enough to guarantee that there is enough content to make the line wrap, thus ensuring that text-align: justify will be applied correctly.

We start with the content: ''; setting, which we use to ensure that the browser "thinks" that there is content to be rendered. We want this content it to be blank, but white-space is okay, since we set the font-size and line-height to 0. If we do not do this, some browsers will consider our "fake" element to be empty, and will set it to display: none;.

The next setting is display: inline-block;. Setting the element to display: inline-block; places the "fake" element on the same line as the .column elements, sets it to be treated as an inline element, and gives it block-styling so that the width property is not ignored.

The width: 100%; setting ensures that the line is long enough to force a line-break. As this is not a real element and there will be no actual, visible content, it does not matter that this element is "shown" on the next line, as it will be invisible.

Lastly, the font-size:0; and line-height: 0; settings are used to ensure that our "fake" element does not take up any visible space.

Completed Styles

CSS
.column-outer-row {
    text-align: justify; /* REQUIRED for all */
    -ms-text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    font-size: 0; /* White-space fix */
}
.column-outer-row:after {
    content: ''; /* Ensure it displays */
    display: inline-block; /* Display on same line as columns */
    width: 100%; /* Ensure row's content is long enough to be justified */
    font-size: 0; /* Ensure (with line-height) does not take up visible space */
    line-height: 0; /* See font-size */
}
.column {
    display: inline-block; /* All major modern browsers */
    *display: inline; /* Browser hack for IE6/7 */
    zoom: 1; /* For IE 6/7 to display inline-block */
    vertical-align: top; /* Prevent staggering */
}

LESS (Alternative)
/* Mixin */
.edc-outer-row(@columnClass, @valign: top, @fontSize: @baseFontSize) {
    text-align: justify; /* REQUIRED for all */
    -ms-text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    text-justify: distribute-all-lines; /* IE ONLY (other browsers use :after) */
    font-size: 0; /* White-space fix */

    /* Restore font-size for children */
    > * {
        font-size: @fontSize;
    }

    >.@{columnClass} {
        display: inline-block; /* All major modern browsers */
        *display: inline; /* Browser hack for IE6/7 */
        zoom: 1; /* For IE 6/7 to display inline-block */
        vertical-align: @valign; /* Prevent staggering */
    }    

    &:after {
        content: ''; /* Ensure it displays */
        display: inline-block; /* Display on same line as columns */
        width: 100%; /* Ensure row's content is long enough to be justified */
        font-size: 0; /* Ensure (with line-height) does not take up visible space */
        line-height: 0; /* See font-size */
    }  
}

/* Rules */
.column-outer-row {
    .edc-outer-row(column);
}

Demo Styles

Now that we have finished creating our reusable styles, let's add in some styles for a demo that uses our markup.

Whether I am using LESS or plain CSS, I am a big fan of making sure that things are reusable. For that reason, we will are not going to modify the reusable styles that we defined. Instead, we will create new style rules to add to or override them.

Because LESS provides the ability to use techniques not available in plain CSS that vastly improve reusability, we will write the CSS for the demo separately from the LESS. As before, let's start by looking at the CSS.

CSS Demo Styles

The CSS style rules for our demo are as follows:

CSS
.demo-outer-row {
    background: rgb(200, 200, 200);
    border: 2px solid black;
    min-width: 630px;
    width: 100%;
    height: 200px;
}

/* Override? */
/*.demo-outer-row > * {
    font-size: 18px; 
}*/

.demo-outer-row > .column {
    width: 136px;
    height: 186px;
    padding: 5px;
    border: 2px dashed rgb(140, 240, 255);
    background: rgb(0, 200, 255);  

    /* Overrides? */
    /*vertical-align: bottom;*/
    /*font-size: 12px;*/
}

Details

The CSS styles are pretty straightforward, so we won't get too in-depth. However, I would add that if you are looking to override the font-size property that we set on the children of the .column-outer-row, or the vertical-align property on the .column elements then now would be the time to do it. However, note that if you want to use a different class for the column or column-outer-row elements, then you need to add that selector to the reusable rules.

LESS (Alternative)

The LESS styles are much more flexible, as LESS allows us to create functions (mixins) and to pass them parameters, including property values and CSS selectors.

LESS
/* Rules */
.column-outer-row {
    .edc-outer-row(column);

    /* Overrides? */
    /*.edc-outer-row(column, bottom);*/
    /*.edc-outer-row(column, bottom, 12px);*/
    /*.edc-outer-row(column, @fontSize: 12px);*/
    /*.edc-outer-row(other-column-class);*/
    /*.edc-outer-row(other-column-class, text-top, 0);*/

    background: rgb(200, 200, 200);
    border: 2px solid black;
    min-width: 630px;
    width: 100%;
    height: 200px;
}

/* Alternatives */
/*.demo-outer-row {
    .edc-outer-row(column);
    ...
}*/

Since we added support for passing the class name of the column elements, the font-size and the vertical-align properties as parameters to our mixin, overriding them properties is as easy as passing an optional parameter. Even if you decide to use a different class for the column elements then you simply change wthe value that you pass for the @columnClass parameter.

Unlike in the CSS demo, here there is one thing that we might go back and change. Because LESS is provides so much more in the way of reusability, it might be better if we remove our rule to call the mixin and instead call it here. This is because the column-outer-row class on element isn't required anymore. Thanks to LESS, we can now call the mixin on any element that we select. In the commented example above, notice that we used the demo-outer-row class instead of the column-outer-row class to demonstrate this point. If we used this rule instead then the style rules would be listed for the demo-outer-row class and not for the column-outer-row.

Demo

Check out the attached demo, at the top of this page. Download the zip and extract it. Inside, you will find two demo files: demo-css.html and demo-less.html. The associated CSS and LESS (with compiled and minified CSS) files are included, as well. Have a look and feel free to reach out to me if you have any questions.

Alternatively, if you decide that you want to see a live demo, then check out my portfolio page. The tiles on that page are spaced using this technique. Note, however, that this site was not built with IE6/7 support, so the attached demo files (which use the same styles and classes we talked about) may be better examples to work from.

1 Comments
Post a Comment

 
  • Comment by Simon Reading on Apr 23, 2015
    Hi Zachary, Firstly, thanks for your tutorial and its depth - I am going to have to seriously look at LESS ! I am finding that this (as CSS version) displays weird behavior on Chrome/Chrome Canary, Firefox, Firefox Developer, Opera, & Safari (for PC). [it does work on IE11] On First load, (or reload) the columns remain left aligned : if you then use ctrl+f5 (full reload) it then magically works... I have uploaded your demo files here : http://www.orangutan.biz/holt_development/demo-css.html (and apart from a minor path and renaming of the CSS file they are vanilla). What makes it doubly weird is that it runs perfectly in all the above browsers on a WAMP Localhost server setup.... Regards Simon