CSS table-row creates a table on-the-fly
A strange issue with the way browsers render CSS table-row elements
At work we have a table of data where we expand an item of the table (when clicked) by inserting a row in between, and filling that new row with more detailed information (using jQuery).
The new row needs to span the complete width of the table, and since we had some issues with getting this to work I thought I'd document my findings for others that might be going through similar issues.
Bear in mind that we're styling the table, so some of the issues might be more/less pronounced depending on how much styling you're doing.
HTML tables redraw when inserting rows
We started off by trying HTML tables, since our data is pretty much tabular, and having real table markup seemed logical, and we'd then have 'free' functionality of the columns lining up.
<table>
<thead>
<tr>
<th>Header</th>
<th>Header</th>
<th>Header</th>
<th>Header</th>
</tr>
</thead>
<tbody>
<tr>
<td>Row 1</td>
<td>Row 1</td>
<td>Row 1</td>
<td>Row 1</td>
</tr>
<tr>
<td colspan="4">Full width row (inserted dynamically)</td>
</tr>
<tr>
<td>Row 2</td>
<td>Row 2</td>
<td>Row 2</td>
<td>Row 2</td>
</tr>
</tbody>
</table>
This approach worked on desktop browsers but in Safari on iOS (iPad with iOS 5 & 6), the insertion process caused a very strange redrawing issue which you could see quite clearly for just less than a second each time.
Swapping to CSS tables
The most simple iteration from the HTML table was to swap all elements to DIVs and then style them as a table using CSS3 display: table
, display: table-row
and display: table-cell
styles.
<div class="table">
<div class="row head">
<div class="cell">Header</div>
<div class="cell">Header</div>
<div class="cell">Header</div>
<div class="cell">Header</div>
</div>
<div class="row">
<div class="cell">Row 1</div>
<div class="cell">Row 1</div>
<div class="cell">Row 1</div>
<div class="cell">Row 1</div>
</div>
<div class="row">
<div class="fullwidth-cell">Full width row (inserted dynamically)</div>
</div>
<div class="row">
<div class="cell">Row 2</div>
<div class="cell">Row 2</div>
<div class="cell">Row 2</div>
<div class="cell">Row 2</div>
</div>
</div>
.table {
display: table;
width: 100%;
}
.row {
display: table-row;
width: 100%;
}
.cell {
display: table-cell;
width: 25%;
}
.fullwidth-cell {
/* OPTION 1 */
display: block; /* or without display definition */
/* OPTION 2 */
display: table-cell;
width: 100%;
}
This is where the tricky issue reared it's head.
Option 1 - display: block
We first choose to make the full-width cell a block
element (and take out the row
container). Again, there didn't seem to be a major issue with desktop browsers, although occasionally some of the table looked misaligned after a full-width div had been inserted and subsequently removed.
On the iPads the issue was even worse though. Each time an expanded row was inserted, it would be placed above the table rather than in-between the rows, where it was situated within the DOM. My hypothesis at this point was that it's treating the CSS table as if it were an HTML table, and the inserted block
element got forced outside of the table, just as a DIV in between HTML table rows would be, and instead rendered above the table.
Knowing that it's not necessary to exactly match the table -> row -> cell heirachy in CSS we decided to remove the display: table
definition from the CSS. We expected that each row would then be an independant block, and although we'd have to be more careful to make sure column boundaries lined up between rows, we should be able to insert block elements without issue because each row would be an independant table-row
block without a table linking them together.
The issue was the same, misalignment and occasional other visual glitches on desktop, and the block is inserted above the table on iPad.
Option 2 - display: table-cell
We then decided to try changing from block
to table-row
or table-cell
with width: 100%
. However, any combination of this CSS caused the full-width block to be the same width of the first cell on the row above/below, so couldn't be used.
You might wonder how you can make it do the same as colspan="4"
from the HTML example in CSS... you can't, which is very annoying. This also exacerbates the issue with the table-rows being connected together on-the-fly by the browser.
So what's actually happening?
Based on experiments, it appears that the browser rendering engine (at least Webkit) detects sibling table-row
elements, and rather than rendering them as ad-hoc one-row tables, it groups them together to create a proper table. This means that overflowing content that makes a cell wider also affects the rest of the table.
At least on desktop (Chrome), when a block
element is inserted in between table-row
elements, the rendering engine breaks up the table it's made for itself into two tables, one above and one below the inserted block, with their own column width matching.
When you change the inserted block to a table-row
and table-cell
, the browser sees a single cell on a row, and since there is no colspan property, matches the cell to the first column, and sets the width property likewise to match the rest of the table.
Solution
Remove the table-row
CSS, and instead rely on block
and the fact that the cell widths add up to 100% to deal with the row separation. The full-width block can then be added without major issues.
Conclusion
Check your CSS widths carefully. We had filled the row completely with content, so had very little room to maneuvre in order to keep all columns on all rows (especially since we use percentage widths rather than pixels). Don't forget to account for padding, margin and borders in your calculations, although if you're using percentage widths tracking the few pixels added by borders is difficult/impossible purely in CSS.
Use a grid where possible. We couldn't easily use a grid framework as each cell needed to be a different width because of the content, but if it would perhaps have made life easier if we could have found a grid system that worked.
Comments