CSS fluid columns, fixed margins; the holy grail of holy grails

Solution 1:

I finally figured out. After into the hundreds of hours wasted on and off over the last decade (though I'm relying on some css that wouldn't have worked year ago anyway). I solved it without any gotchas. and in IE8+.

Please prepare the 2001: A Space Odyssey Music because I'm landing this boat.

The genius and trick to this method is in using inline-block elements and then using word-spacing to counterbalance using a negative right margin. A negative right margin on it's own will pull elements together, allowing you to have 100% width set and still fit things in between, but leave the elements overlapping. Setting negative margin on the parent just undoes the child margin in regards to the effect on interacting with total width (the magic "100% width" mark we're trying to hit"). Padding only serves to increase the size of the element its on and is useless with regards to counter-acting margin. It is often used with box-sizing in the jury rigged solutions to this problem, at the expense of losing the ability to use padding at all otherwise (and margin) and likely requiring more wrapper elements.

word-spacing provides the magical "third way" to add or remove horizontal distance between two elements, provided they are inline-block, since they will be counted as a single "word" in that case, and any whitespace between will collpapse down to the one single controllable "word-spacing" property. Aside from this trick I'm not aware of another way to get this 100% result.

I humbly present the ultimate answer to the fixed-gutters flex-columns problem. I hereby name my solution "the omega maneuver". It comes with the ability to handle arbitrary mixed width columns (adding up to 100% total width exactly or slightly less for rounding), any gutter size, any predefined amount of columns in width, handles arbitrary amounts of rows with auto-wrapping, and uses inline-block elements so therefore provides the vertical-alignment options that come with inline-block, AND it doesn't require any extra markup and only requires a single class declaration on the container (not counting defining column widths). I think the code speaks for itself. Here's the code implementation for 2-6 columns using 10px gutters and bonus helper classes for percentages.

EDIT: interesting conundrum. I've managed to get two slightly different versions; one for mozilla and ie8+, the other for webkit. It seems the word-spacing trick doesn't work in webkit, and I don't know why the other version works in webkit but not ie8+/mozilla. Combining both gets you coverage over everything and I'm willing to bet there's a way to unify this tactic or something very similar to work around the issue.

EDIT2: Mostly got it! Magical text-align: justify gets WebKit almost there with the word-spacing one. The spacing just seems a tiny bit off, like a matter of pixels on the right and maybe one extra in the gutters. But it's usable and it seems more reliable about keeping the columns than anything I've used before. It never chops down to fewer columns, it'll compress until the browser gets a horizontal scrollbar.

Edit3: Got it a little close to perfect. Setting the font-size to 0 normalizes most of the remaining issues with spacing that's off. Just gotta fix IE9 now which collapses it if it font is size 0.

EDIT4: Got the answer to IE from some other fluid width posts: -ms-text-justify: distribute-all-lines. Tested in IE8-10.

/* The Omega Maneuver */
[class*=cols] { text-align: justify; padding-left: 10px; font-size: 0;
             -ms-text-justify: distribute-all-lines; } 

 [class*=cols]>* { display: inline-block; text-align: left; font-size: 13px;
                word-spacing: normal; vertical-align: top;
                -webkit-box-sizing: border-box;
                   -moz-box-sizing: border-box;
                        box-sizing: border-box; }

.cols2 { word-spacing: 20px; padding-right: 20px; }
.cols3 { word-spacing: 30px; padding-right: 30px; }
.cols4 { word-spacing: 40px; padding-right: 40px; }
.cols5 { word-spacing: 50px; padding-right: 50px; }
.cols6 { word-spacing: 60px; padding-right: 60px; }

  .cols2 > * { margin-right: -10px; }
  .cols3 > * { margin-right: -20px; }
  .cols4 > * { margin-right: -30px; }
  .cols5 > * { margin-right: -40px; }
  .cols6 > * { margin-right: -50px; }

Some helpers:

.⅛, .⅛s >* { width: 12.50%; }
.⅙, .⅙s >* { width: 16.66%; }
.⅕, .⅕s >* { width: 20.00%; }
.¼, .¼s >* { width: 25.00%; }
.⅓, .⅓s >* { width: 33.00%; }
.⅜, .⅜s >* { width: 37.50%; }
.⅖, .⅖s >* { width: 40.00%; }
.½, .½s >* { width: 50.00%; }
.⅗, .⅗s >* { width: 60.00%; }
.⅝, .⅝s >* { width: 62.50%; }
.⅔, .⅔s >* { width: 66.00%; }
.¾, .¾s >* { width: 75.00%; }
.⅘, .⅘s >* { width: 80.00%; }
.⅚, .⅚s >* { width: 83.33%; }
.⅞, .⅞s >* { width: 87.50%; }
.blarg-five-twelfs { width: 41.66%; }

You can witness my magnum opus in action amongst a field of glory here: http://jsfiddle.net/xg7nB/15/

<div class="cols4">
    <div class="⅙">This is my magnum opus</div>
    <div class="¼">I finally beat css</div>
    <div class="⅙">⚉ ☺ ☻ ♾ ☢</div>
    <div class="blarg-five-twelfs">I BEAT IT FOREVER</div>
</div>

The absolute minimal implementation, using as an example 4 equal width (25%) width cols and 10px gutters is like so:

.fourEqualCols { word-spacing: 40px; padding: 0 40px 0 10px;
                 text-align: justify; font-size: 0;
                 -ms-text-justify: distribute-all-lines; }

.fourEqualCols>* { margin-right: -30px; width: 25%;
                   display: inline-block; word-spacing: normal;
                   text-align: left; font-size: 13px; }


<div class="fourEqualCols ">
  <div>GLORIOUSLY CLEAN MARKUP</div>
  <div>I hate extra markup and excessive class props</div>
  <div>Naked code</div>
  <div>get intimate</div>
</div>

Soooo this code essentially replaces pretty much any existing grid framework right? If you can arbitrarily set gutters and then just make sets of columns that hit 100% width, that's strictly superior to most/all grid frameworks in fact isn't it? If you're not developing for IE7 anymore like a lot of us then that combined with box-sizing: border-box renders padding and border also a non-issue.

Edit: oh right you wanted to be flush with the sides of the container. No problem with this, I had to specifically add side gutters so we can just change some values by 10 and get rid of the padding and voila. http://jsfiddle.net/bTty3/

[class^=cols] { text-align: justify; font-size: 0;
             -ms-text-justify: distribute-all-lines; } 

 [class^=cols] >* { display: inline-block; text-align: left; font-size: 13px;
                word-spacing: normal; vertical-align: top;
                -webkit-box-sizing: border-box;
                   -moz-box-sizing: border-box;
                        box-sizing: border-box; }

.cols2 { word-spacing: 20px; padding-right: 10px; }
.cols3 { word-spacing: 30px; padding-right: 20px; }
.cols4 { word-spacing: 40px; padding-right: 30px; }
.cols5 { word-spacing: 50px; padding-right: 40px; }
.cols6 { word-spacing: 60px; padding-right: 50px; }
 .cols2 >* { margin-right: 0 }
 .cols2 >* { margin-right: -10px; }
 .cols3 >* { margin-right: -20px; }
 .cols4 >* { margin-right: -30px; }
 .cols5 >* { margin-right: -40px; }
 .cols6 >* { margin-right: -50px; }

Same html

<div class="cols4">
    <div class="⅙">This is my magnum opus</div>
    <div class="¼">I finally beat css</div>
    <div class="⅙">⚉ ☺ ☻ ♾ ☢</div>
    <div class="blarg-five-twelfs">I BEAT IT FOREVER</div>
</div>

I beat CSS here's your proof

Solution 2:

Try this pure CSS2 solution: demo fiddle

Base CSS (fiddle without the cosmetics):

html, body {
    padding: 0;
    margin: 0;
}
#wrap {
    padding-right: 30px;
    overflow: hidden;
}
.col {
    float: left;
    margin-left: 40px;
    margin-right: -30px;
}
.col:first-child {
    margin-left: 0;
}
.small {
    width: 16.66%;
}
.medium {
    width: 25%;
}
.large {
    width: 41.66%;
}

HTML:

<div id="wrap">
    <div class="col small"></div>
    <div class="col medium"></div>
    <div class="col small"></div>
    <div class="col large"></div>
</div>

Tested on Win7 in IE7, IE8, IE9, Opera 11.50, Safari 5.0.5, FF 6.0, Chrome 13.0.


Update:

Now, if you like this to work with an arbitrary number of columns, you have to add an extra class to the container specifying the column count:

<div class="cols-12 count-04">
    <div class="col width-02"></div>
    <div class="col width-03"></div>
    <div class="col width-02"></div>
    <div class="col width-05"></div>
</div>

See this updated fiddle demonstrating a number of various column counts.

Possible bug:

Theoretically, imho this solution should work for any number of columns for every possible minimum column width in any browser window width. But it seems, all browsers prove not being able to handle: 1. a large number of 1 column width columns, or 2. a small browser window width.

Note that all browsers with a minimum width of 1440 pixels, which equals 12 times 120 pixels (the space occupied by all 10px margins), handle the solution just fine. And when you use 2 or more column width colums, the requirement for the minimum browser width indeed drops to 720 pixels (6 * 120px). This last case sounds more realistic to be, but still, I cannot explain this browser behaviour.

I tried fixing the issue by introducing an additional last column class as demonstrated by this fiddle, but it does not solve the problem for small browser widths. It dóes solve a tiny rounding error due to the broken width percentages though, but that issue could be ignored I suppose.

I would like to hear from other css experts on this one, so I added a bounty.

Solution 3:

Why don't you use

.column > .panel {
    padding: 10px 0 10px 10px;
}

.column:first-child > .panel {
    padding-left: 0px;
}

It will make 10px spaces only between boxes and without using last-child.