Jaeger Technical Specifications
Country Height (meters) Weight (tonnes)
Gipsy Danger United States 79 1980
Striker Eureka Australia 76 1850
Crimson Typhoon China 76 1722
Coyote Tango Japan 86 2312
Cherno Alpha Russia 85 2412

Previously I’ve shown & patterns for various kinds of , including designs with zebra stripes, rounded corners and scrolling data. Things get slightly more complex when we turn to “data grids”: tables that feature headers for both column and row information.

For this example I’ll use technical specifications of the giant Jaeger fighting mechs in Guillermo del Toro’s Pacfic Rim because, well, why the heck not.

A “data grid” table, as I define it, has headers for both rows and columns, with no visible cell in the top left corner. Like most challenges in web development, the key is employing the right markup, with tags that enhance :

 <table id="jaeger-specs">
	<caption>Jaeger Technical Specifications</caption>
			<th scope="col">Country
			<th scope="col" >Height<span> (meters)</span> 
			<th scope="col" >Weight (tonnes)</span>
			<th scope="row">Gipsy Danger
			<td>United States
			<th scope="row" >Striker Eureka
			<th scope="row">Crimson Typhoon
			<th scope="row">Coyote Tango
			<th scope="row">Cherno Alpha

I am using scope to signify the role table header cells play – i.e. whether they are headers for rows or columns – together with HTML5 shortcuts. Note that despite the use of shortcuts the very first table header cell has a closing tag: this will prove important in applying the CSS to come.

The selectors I’m using in the stylesheet, which include :not and :empty, are deliberately advanced. It’s entirely possible to create this same table appearance using more traditional fare, such as classes, but my goal is to use the most efficient CSS code possible. A particular issue to avoid is doubling up of borders: you don’t want one cell wall in the table to appear thicker than the others due to a border on both it and a neighbouring element. To that end:

#jaeger-specs {
	border-collapse: collapse;
	margin: 0 auto;
#jaeger-specs td,
	#jaeger-specs th { 
		text-align: center;
		padding: 1rem;
#jaeger-specs th[scope] {
	color: #fff;
	background-color: #000;
	font-weight: normal;
#jaeger-specs tbody { 
	border: 1px solid #222;
#jaeger-specs tbody th,
	#jaeger-specs tbody th + td { 
		text-align: left;
		font-weight: normal;
#jaeger-specs tbody th {
	font-size: 1.4rem;
	border-right: 1px solid #222;
	background-size: cover;
	font-family: Agency, sans-serif;
	text-shadow: 1px 1px 1px rgba(0,0,0,0.6);

The central body of the table has a border all the way around it. The table header cells for the rows and those immediately adjacent to them are left-aligned, with a border between them. The content of all other cells is centered.

#jaeger-specs tbody tr:nth-child(1) th {
	background: url(gipsy-danger.jpg);
#jaeger-specs tbody tr:nth-child(2) th {
	background: url(striker-eureka.jpg);
#jaeger-specs tbody tr:nth-child(3) th {
	background: url(crimson-typhoon.jpg);
#jaeger-specs tbody tr:nth-child(4) th {
	background: url(coyote-tango.jpg);
#jaeger-specs tbody tr:nth-child(5) th {
	background: url(cherno-alpha.jpg);

Rather than identifying each row with a class or id, I’m simply counting where it is in the body of the table to apply an appropriate background image. This has the advantage of keeping markup light, but contains the drawback that changing the order of the rows will mean a small rewrite of the CSS.

#jaeger-specs tbody tr:not(:last-child) {
	border-bottom: 1px solid #222;

Every row in the table except the last one has a border on its bottom edge.

#jaeger-specs thead th[scope] { 
	border: 1px solid #222;
	border-bottom: none;

Every table header cell with a scope attribute has a border on all sides, with the border on the bottom cancelled with a separate rule.

#jaeger-specs tbody td:not(:last-child) {
	border-right: 1px solid #222;
#jaeger-specs tbody td { 
	background: hsl(195, 100%, 30%);
	color: #fff;

All the data cells in the table have a background color specified in HSL. Every cell, with the exception of the last in each row, has a border on its right side.

That’s a data grid, probably the most complex table most web designers will encounter in day-to-day development. It’s by no means the last thing we can do with tables, as you’ll see in articles to come.

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/bBWPZB