The Films of Hayao Miyazaki
FilmYearHonor
My Neighbor Totoro1988Blue Ribbon Award (Special)
Princess Mononoke1997Nebula Award (Best Script)
Spirited Away2001Academy Award (Best Animated Feature)
Howl’s Moving Castle2004Hollywood Film Festival (Animation OTY)

Recently Geoff Yuen demonstrated a very effective technique for creating . 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 , the same could be achieved with , 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 – 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