Recently Geoff Yuen demonstrated a very effective technique for creating responsive HTML tables. I had just two small issues with the demo: the CSS was written in Sass, and there was a great deal of replication and redundancy in the use of data
attributes. The solution shown here addresses both issues. You’ll need to view this page in a browser window less than 600 pixels wide to witness the transition into the alternative layout of the table.
The Base Code
I’ll start with a close interpretation of Geoff’s code:
<table id="miyazaki">
<caption>The Films of Miyazaki</caption>
<thead>
<tr><th>Film<th>Year<th>Honor
<tbody>
<tr>
<td data-th="Film">My Neighbor Totoro
<td data-th="Year">1988
<td data-th="Honor">Blue Ribbon Award (Special)
<tr>
<td data-th="Film">Princess Mononoke
<td data-th="Year">1997
<td data-th="Honor">Nebula Award (Best Script)
<tr>
<td data-th="Film">Spirited Away
<td data-th="Year">2001
<td data-th="Honor">Academy Award (Best Animated Feature)
<tr>
<td data-th="Film">Howl’s Moving Castle
<td data-th="Year">2004
<td data-th="Honor">Hollywood Film Festival (Animation OTY)
</table>
Note the repetition of the data
attribute, and the reflection of the appropriate header content in each table cell. The base CSS:
table#miyazaki caption {
font-size: 2rem;
color: #444;
margin: 1rem;
background-image: url(miyazaki.png), url(miyazaki2.png);
background-size: contain;
background-repeat: no-repeat;
background-position: center left, center right;
}
table#miyazaki {
border-collapse: collapse;
font-family: Agenda-Light;
font-weight: 100;
background: #333;
color: #fff;
text-rendering: optimizeLegibility;
border-radius: 5px;
}
table#miyazaki thead th {
font-weight: 600;
}
table#miyazaki thead th,
table#miyazaki tbody td {
padding: .8rem;
font-size: 1.4rem;
}
table#miyazaki tbody td {
padding: .8rem;
font-size: 1.4rem;
color: #444;
background: #eee;
}
table#miyazaki tbody tr:not(:last-child) {
border-top: 1px solid #ddd;
border-bottom: 1px solid #ddd;
}
The responsive CSS for converting the table:
@media screen and (max-width: 600px) {
table#miyazaki caption {
background-image: none;
}
table#miyazaki thead {
display: none;
}
table#miyazaki tbody td {
display: block;
padding: .6rem;
}
table#miyazaki tbody tr td:first-child {
background: #333;
color: #fff;
}
table#miyazaki tbody td:before {
content: attr(data-th);
font-weight: bold;
display: inline-block;
width: 6rem;
}
}
The media query hides the header cells of the table and displays the hidden data-th
value as a label in front the contents of each table cell, on its own line. Special treatment is given to the first cell for each transformed row, to keep the data distinct.
Automating The Result
This works very well, as you can see if you resize the browser window. However, it’s not terribly scalable: adding a new row means manually inserting data
attributes for each table cell. While that work could be done with a server-side language such as PHP, the same could be achieved with JavaScript, if you were willing to sacrifice some progressive enhancement.
First, we simplify the table:
<table id="miyazaki">
<caption>The Films of Hayao Miyazaki</caption>
<thead>
<tr>
<th>Film
<th>Year
<th>Honor
<tbody>
<tr>
<td>My Neighbor Totoro
<td>1988
<td>Blue Ribbon Award (Special)
<tr>
<td>Princess Mononoke
<td>1997
<td>Nebula Award (Best Script)
<tr>
<td>Spirited Away
<td>2001
<td>Academy Award (Best Animated Feature)
<tr>
<td>Howl’s Moving Castle
<td>2004
<td>Hollywood Film Festival (Animation OTY)
</table>
At the end of the document, add a small script:
var headertext = [];
var headers = document.querySelectorAll("#miyazaki th"),
tablerows = document.querySelectorAll("#miyazaki th"),
tablebody = document.querySelector("#miyazaki tbody");
for(var i = 0; i < headers.length; i++) {
var current = headers[i];
headertext.push( current.textContent.replace( /\r?\n|\r/,"") );
}
for (var i = 0, row; row = tablebody.rows[i]; i++) {
for (var j = 0, col; col = row.cells[j]; j++) {
col.setAttribute("data-th", headertext[j]);
}
}
In English, this script gains the text content of each <th>
cell, stripping it of return and newline characters. That text is then applied as a data
attribute value to the appropriate table cells, yielding the same result if the CSS is included. (setAttribute
is used rather than dataset
, as the latter is only just supported in IE 11.)
The result is still accessible – the inserted DOM text is readable in VoiceOver, for example – although it will require more testing.
Enjoy this piece? I invite you to follow me at twitter.com/dudleystorey to learn more.
Check out the CodePen demo for this article at https://codepen.io/dudleystorey/pen/Geprd