2 notes &
Simulating colspan with CSS in a fluid layout, Part 1 supplement
This is a supplement to the first part of a five-part series of posts that detail my adventures in trying to achieve a colspan effect with the elements of a CSS layout. The posts follow this rough outline:
- table trouble
- script solutions
- Opera issues
- subpixel issues
- conclusions
Recap
In the previous post, I introduced some of the challenges in trying to achieve a colspan effect when applying a fluid, tabular layout to non-tabular content. I also described an additional constraint for the layout, that the rendered widths of two cells in the same row must be equal at the pixel level if their specified percentage widths are equal.
I eliminated the use of an HTML table as a matter of best practice. I also eliminated the use of a CSS table (which is used by default to style an HTML table, anyway), mostly due to the idiosyncratic distribution of leftover width of the table across columns of the table. Here I describe some alternatives to using a CSS table, before discussing the colspan effect in part 2.
What’s a fluid layout? (a preliminary)
Both the width and the height of an element can be defined in three ways:
- relative to the height of the parent (specified as a percentage)
- absolute (specified as a length)
- relative to the heights of the children (shrink-to-fit)
Additionally, the height of an element can be defined relative to the width of the element (fixed aspect ratio), though there’s a bit of a trick to doing that. As a caveat, I should note that each option is not necessarily available for each of the ways in which an element can be styled. (This will make things more complicated in part 3.)
In a fluid layout, the width of each immediately contained box in at least one, possibly non-contiguous vertical extension of the canvas should be proportional to the width of the viewport of the user agent. Or, if you don’t read specification-ese: the width of each element in at least one top-to-bottom column of the page should be proportional to the width of the window of the browser. That’s technically too strict a definition, though it will suffice. In the following example, the width of each fluid element (shown in grey) is expressed as a percentage of the width of the page (the dashed box), which is the same as the width of the window (the solid box). The width of each element in another column may be defined in any way, though often the widths of the elements in the same column are all defined in the same way.

fluid layout
On the other hand, no element has a restriction on the definition of its height. Generally, the height of a fluid element will grow in response to a shrinking width, and vice versa. That’s an important consideration when laying out a fluid table.
What’s a fluid table?
Although I have explicitly eliminated the use of both an HTML table and a CSS table, I will continue to use the table nomenclature. I’m not really talking about a table, though. As mentioned in the previous post, I’m talking about controlling the horizontal and vertical alignment of the rectangular components of a visual presentation. I’m talking about a grid layout or a template layout. The row and column alignment is simply an aesthetic.
In the absence of a grid or a template, neither of which is natively supported by modern browsers as of this writing, another mechanism must be used to style elements in a table-like fashion. I can think of four options:
- explicitly positioned elements
- lines of inline elements (or inline-block elements) in the normal flow
- floated elements
- some combination of the above
In case you’re wondering, not only is a multi-column layout unsupported by IE 9, but the width of each column cannot be individually controlled, making a multi-column layout unsuitable as a tabular layout in general. Some (small) potential exists to abuse ruby boxes as a tabular layout, though this is unsupported by IE 9 and Opera 11.5. More promising are flexible boxes, though these also are unsupported by IE 9 and Opera 11.5. So, I’m stuck with the above four options.
In a fluid table, the width of each cell of at least one column of the table should be proportional to the width of the table.
A fluid table of explicitly positioned elements
Explicitly vertically positioned elements would be problematic if the height of each cell of the table is not predetermined. Since a fluid element has no restriction on the definition of its height, vertically positioned elements would be useful for only a limited subset of fluid layouts.
Explicitly horizontally positioned elements would simply imitate the horizontal adjacency of inline elements or floated elements. There’s no clear benefit to doing this.
Regardless of whether elements are vertically positioned and/or horizontally positioned, explicitly positioning every cell of a table—even a small table—would possibly require JavaScript, and it would certainly be a maintenance headache. No, it’s better to let the elements “flow” together as a table.
A fluid table of lines of inline elements
A line of inline elements seems like a good match for a table row. Consider the following document fragment.
<div class="TableRow">
<div id="a"></div>
<div id="b"></div>
</div>
The structure will be styled as a single row, which has two cells.
.TableRow {
background-color: #CCCCCC;
}
#a, #b {
display: inline-block;
height: 5em;
width: 25%;
}
#a {
background-color: #FF0000;
}
#b {
background-color: #0000FF;
}
Each of the two cells is specified to occupy 25% of the width of the containing block. You might expect to see 25% red and 25% blue.

expected layout of a table row with inline cells
Instead, you would see some extra grey.

actual layout of a table row with inline cells
The first problem is not immediately apparent, and indeed not applicable to this example given that the width of every cell is defined relative to the containing block. Imagine a fixed layout, in which the width of each cell is defined as a length. If the width of the viewport is less than the cumulative width of the cells, as inline elements they will wrap. This can be corrected by adding white-space:nowrap to the style of a row. (Depending on how the containing block is defined, this may cause the cells to overflow.)
The second, more obvious problem is the unanticipated grey. The box of the row can be seen below the cells, between the cells, and to the right of the last cell. I’ll address each one in turn.
The padding below the cells is caused by the baseline of the box of each cell being aligned by default with the baseline of the box of the row. The reason for this is worthy of its own post and not particularly relevant to the current discussion, so I’ll save it. Suffice it to say that in this case the row can be made the same height as its contained cells by adding vertical-align:top or vertical-align:bottom to the style of each cell.

layout of a table row
with no padding below inline cells
The padding between the cells is caused by the whitespace between the div elements in the HTML document. The whitespace consists of a linefeed (or some other newline sequence, prior to normalization), followed by tabs and/or spaces. This whitespace is reduced by the CSS whitespace processing model to a single space, which is rendered between the div elements as a gap. The gap can be removed in four, distinct ways.
The simplest way to remove the gap between the cells is to remove the causal whitespace from the HTML document. This can make for ugly, unmaintainable code, however.
<div class="TableRow">
<div id="a"></div><div id="b"></div>
</div>
The second way to remove the gap between the cells is to add a zero-width space at the beginning or at the end of the causal whitespace.
<div class="TableRow">
<div id="a"></div>​
<div id="b"></div>
</div>
The CSS3 module on text has this to say about collapsing whitespace:
A zero width space before or after a white space sequence containing a newline causes the entire sequence of white space to collapse into a zero width space.
Browsers tend not to implement this clarified behavior, instead collapsing any sequence of whitespace into a single space. Regardless, peppering the HTML document with character references solely to eliminate layout glitches hardly seems like a good idea.
The third way to remove the gap between the cells is to remove each text-node child of the row element. This requires manipulating the DOM via JavaScript, presumably when the HTML document is loaded. (Yes, I know my JavaScript is not maximally efficiently.)
var textNodes = new Array();
for (var i = 0; i < row.childNodes.length; i++) {
var childNode = row.childNodes[i];
if (childNode instanceof Text) {
textNodes.push(childNode);
}
}
for (var i = 0; i < textNodes.length; i++) {
var textNode = textNodes[i];
row.removeChild(textNode);
}
The fourth way to remove the gap between the cells requires applying the humorously named bikeshedding property to the row. Adding bikeshedding:discard to the style of the row would effectively ignore the causal whitespace. Unfortunately, the bikeshedding property is not yet supported.
So, the cells can be horizontally compacted in different ways, but the only desirable method (the bikeshedding property) isn’t available. That leaves a suboptimal solution, but a solution nonetheless.

layout of a table row
with no padding between inline cells
The padding to the right of the cells is caused by the block-level display of the row. The row has no specified width, so its width is calculated as the width of its containing block. The box of the row could instead be made a shrink-to-fit box by adding display:inline-block to the style of the row. If you try this after removing the padding below and between the cells, the row will render as a zero-width box. Er, what?
The problem is that the width of the row is dependent on the cumulative width of the cells, and the width of each cell is dependent on the width of the row. In this case, the layout remains undefined by the CSS2.1 specification. If the width of an outer element and the width of an inner element are mutually dependent, a browser is free to do anything with the width of the inner element. Typically, the width of such an inner element is pre-calculated as 0. The width of the outer element is then calculated normally as the cumulative width of the inner elements, and the width of the inner element is then re-calculated normally with respect to the width of the outer element.
To see this in action, consider the table row from our running example, with the padding below and between the cells already removed.
<div class="TableRow">
<div id="a"></div><div id="b"></div>
</div>
The width of the first cell is specified as half the width of the row. The width of the second cell is instead specified as a length.
.TableRow {
display: inline-block
background-color: #CCCCCC;
}
#a, #b {
display: inline-block;
vertical-align: bottom;
height: 5em;
}
#a {
width: 50%;
background-color: #FF0000;
}
#b {
width: 5em;
background-color: #0000FF;
}
The rendered width of the first cell is indeed 50% of the width of the row. The width of the row, however, is determined solely by the width of the second cell, which gets pushed below the first cell due to line wrapping.

layout of a table row
with mutually dependent widths
While this looks suspiciously like the beginnings of a colspan effect, the resultant layout is not part of the specification. Moreover, the width of the spanned cell is specified as a length rather than as a percentage, making for a fixed table rather than a fluid table.
Back to the padding to the right of the cells. The box of the row cannot be a shrink-to-fit box. The width must be explicitly specified as a length or as a percentage. In order to retain the fluidity of the table, the width of the row must be specified as a percentage. The specified width of each cell must then be scaled relative to the specified width of the row.
The example row has two cells, each with a width that is 25% of the row. When the row had a block-level display, the width of the row was implicitly the width of its containing block. Now, the width of the row must be specified as the sum of the widths of the cells:
width-of-row = width-of-cell-a + width-of-cell-b
= 25% + 25% = 50%
The width of each cell must then be scaled:
new-width-of-cell = old-width-of-cell / width-of-row
= 25% / 50% = 50%
The table row now appears correctly styled.

layout of a table row of inline cells
The padding is gone. Provided the row is relatively high. If the row is relatively short—0.5em, say—some padding will reappear below or above the cells, depending on whether vertical-align:top or vertical-align:bottom, respectively, was added to the style of each cell.

layout of a table row
with padding above short inline cells
The problem is that the height of the row is less than the line height as specified by the default imaginary strut. This default line height is a multiple of the font size of the row. The specification recommends the multiple be between 1.0 and 1.2, though a browser can use any “reasonable” value. In this case the row can once again be made the same height as its contained cells by adding line-height:0 to the style of the row.

layout of a table row of short inline cells
In order to layout multiple rows, rows could be vertically stacked. Alternatively, the cells of multiple rows could be nested within the same container.
<div class="Table">
<div id="a"></div><div id="b"></div><div id="c"></div><div id="d"></div>
</div>
Each sequence of cells whose cumulative width is 100% (or more precisely, not greater than 100%) represents a row, since the cells will wrap at that point.
.Table {
display: inline-block
line-height: 0;
background-color: #CCCCCC;
}
#a, #b, #c, #d {
display: inline-block;
vertical-align: bottom;
height: 5em;
width: 50%;
}
#a, #d {
background-color: #FF0000;
}
#b, #c {
background-color: #0000FF;
}

layout of a table of inline cells
This works even when the rows have different height.
#a, #b {
height: 5em;
}
#c, #d {
height: 2.5em;
}

layout of a table of inline cells
with rows of different height
So far, the height of each cell of a row has been assumed to be known and/or fixed. When this assumption holds, using a line of inline elements for one or more table rows will work. Either the common height can be explicitly set (as in the examples), or the same zero-width strut can be placed at the beginning of each cell. Even when this assumption does not hold, using a line of inline elements for a table row can still work. Provided no cell of the row has a border or a background, the differences in height would be imperceptible.
However, when the height of each cell in a row is neither known nor fixed, and when at least one cell of the row has a border or a background, the height of each cell of the row must be “stretched” to the height of the tallest cell in order to preserve the table effect. How can this be done without JavaScript? The short answer is that it can’t be done, but it can be faked. Luckily, a technique is known for faking it: the faux-column Holy Grail fluid layout.
This technique relies on floated elements. Though the pursuit of simulating a fluid table with lines of inline elements has resulted in a lovely learning journey, the already imperfect solution is insufficient for the general case. It’s time to move on.
A fluid table of floated elements
Rather than rehash the faux-column Holy Grail layout, I’ll reference the excellent description by Matthew James Taylor. This technique has three weaknesses, however:
- When a non-floated element with an unspecified height (in this case, the body element) contains only floated elements, the calculated height of the non-floated element is 0. This happens because floated elements are removed from the normal flow, and the height of the container is a shrink-to-fit height.
- The cumulative pixel width of the columns may be less than the sum of their individual percentage widths, due to subpixel rounding. The rounding error increases with the number of columns. The unintended result is that the background of the first (or last) column may appear wider than intended.
- The width of each background is actually the common width of the containers, not the width of the column. This can cause issues with borders and “shaped” backgrounds, such as those produced using rounded corners or transformations.
The first issue can be addressed by adding an empty “clear” element as the last child of the non-floated container.
<div class="NonFloatedContainer">
<div class="FloatedElement">…</div>
<div class="ClearElement"></div>
</div>
The clear element should have no height in order to ensure that it occupies no space in the normal flow. It should have a width equal to that of the container in order to ensure that it is positioned directly below the last floated element.
.ClearElement {
clear: both;
height: 0;
width: 100%;
}
Since the shrink-to-fit height of the container must necessarily include the (non-visible) clear element, the height of the container will encompass the floated element as well.
The second issue can be addressed by adding a dummy first (or last) column that will absorb any leftover pixels rounded away when applying the percentage widths. The background color of this column must be the background of whatever element contains the table, since it must “erase” the overflow of the background of the second (or second-to-last) column. This may not be feasible if the background of the containing element of the table is complex.
The third issue cannot be addressed so easily. Ensuring the width of each background is the same as the width of the corresponding column is tantamount to ensuring that the height of each column is the same. JavaScript is necessary.
A combination of lines of inline elements and floated elements
When simulating a table row, a line of inline elements alone is insufficient when the height of each cell of the row is neither known nor fixed, and when at least one cell of the row has a border or a background. On the other hand, floated elements alone are insufficient when the container of the table has a complex background, or when at least one element of the row has a “shaped” background. That suggests the following (admittedly simplified) flowchart.

When either a line of inline elements or floated elements can be used, floated elements are preferred due to the imperfect solution required by a line of inline elements. When neither can be used, JavaScript is necessary. Some additional tricks can be had with JavaScript, in particular to avoid a recalculation of the common height of each cell in response to every resize, though that discussion is saved for another day.