A few smart folks have already put together their thoughts on responsive tables and, while I think the proposed methods are pretty good, I think there might be room for improvement. As such, I’ve been tinkering for a while and came up with the following strategy when it comes to tables.
Step 1: Use data-*
attributes to hold information about the column header(s) associated with the markup:
<table> | |
<thead> | |
<tr> | |
<th scope="col">Name</th> | |
<th scope="col">Email</th> | |
<th scope="col">Dept, Title</th> | |
<th scope="col">Phone</th> | |
</tr> | |
</thead> | |
<tbody> | |
<tr class="vcard"> | |
<th scope="row" class="n" data-title="Name"> | |
<b class="family-name">Smith</b>, | |
<b class="given-name">Laura</b> | |
</th> | |
<td data-title="Email"> | |
<a class="email" href="mailto:laura.smith@domain.com">laura.smith@domain.com</a> | |
</td> | |
<td data-title="Dept, Title">Biology, Director</td> | |
<td class="tel" data-title="Phone"> | |
<a href="tel:+1123456789">123-456-789</a> | |
</td> | |
</tr> | |
<tr class="vcard"> | |
<th scope="row" class="n" data-title="Name"> | |
<b class="family-name">Johnson</b>, | |
<b class="given-name">Ron</b> | |
</th> | |
<td data-title="Email"> | |
<a class="email" href="mailto:ron.johnson@domain.com">ron.johnson@domain.com</a> | |
</td> | |
<td data-title="Dept, Title">Purchasing, Director</td> | |
<td class="tel" data-title="Phone"> | |
<a href="tel:+11234567891">123-456-7891</a> | |
</td> | |
</tr> | |
</tbody> | |
</table> |
Step 2: When the screen is below a certain threshold, set the table
elements to display: block
(thereby linearizing the table), hide the thead
where assistive tech won’t see it, and use generated content to expose the data-*
attributes. Here’s a snippet of SASS & Compass that does that:
// undo tables for small screens | |
// $break-4 is the px-width break at which you want to cut it off | |
@media (max-width: px-to-ems($break-4 - 1px)) { | |
// make each table separate from other ones | |
table { | |
border: 0; | |
@include trailing-border; | |
padding-bottom: 0; | |
display: block; | |
width: 100%; | |
// make sure captions are displayed | |
caption { | |
display: block; | |
} | |
/* | |
* wipe the thead from the face of the earth | |
* modern screen readers will expose the | |
* generated content | |
*/ | |
thead { | |
display: none; | |
visibility: hidden; | |
} | |
/* | |
* make everything display block so it | |
* aligns vertically | |
*/ | |
tbody, tr, th, td { | |
border: 0; | |
display: block; | |
padding: 0; | |
text-align: left; | |
white-space: normal; | |
} | |
// give each row a little space | |
tr { | |
@include trailer; | |
} | |
/* Labeling | |
* adding a data-title attribute to the cells | |
* lets us add text before the content to provide | |
* the missing context | |
* | |
* Markup: | |
* <td data-title="Column Header">Content Here</td> | |
* | |
* Display: | |
* Column Header: Content Here | |
*/ | |
th[data-title]:before, | |
td[data-title]:before { | |
content: attr(data-title) ":\00A0"; | |
font-weight: bold; | |
} | |
th:not([data-title]) { | |
font-weight: bold; | |
} | |
// hide empty cells | |
td:empty { | |
display: none; | |
} | |
} | |
} |
We’ve been using this approach on a number of sites currently in development and it works really well. I put together a demo of this technique so you could play around with it yourself.
Notes:
- I chose to use a
data-*
attribute (data-title
) instead oftitle
as thetitle
attribute could be read out by assistive technology and in the case of thethead
being available as well (when notdisplay: none
), resulting in the information being read twice (which is not ideal). That’s not a certainty however, so you could choose to go thetitle
route if that’s your preference. I prefer to avoid the potential issue. - If you have multiple header rows over a cell (say a parent row and then a child row), I’d recommend making the
data-title
something like “Parent Header - Child Header.” - While you could use JavaScript to auto-generate the
data-title
attributes by referencing the column headers, I feel this is information that should exist even if JavaScript is not available. You may disagree.
Comments
Hi. Brillant idea ! Thanks for your work and time. I get white screen on your Demo with iPhone 5, safari iOS 6.1 or last version of chrome.
I see the white screen on iPhone as well. I think it is a Codepen issue. I should be able to point to this in use on a live site very soon and I promise that will work on the iPhone ;-)
These days, everyone is jumping on the responsive bandwagon. Apart from responsive layouts, everything is going responsive: sliders, lightboxes, galleries, you name it! But then one day, when I was working on an HTML table with a lot of columns, I thought “How is this going to look on my iPhone?”. And then I searched for “responsive tables” and found your blog on a few solutions out here. Thanks.
You may want to add “visibility:hidden;” to the THEAD and TD:EMPTY ( at least according to A11y Project - http://a11yproject.com/posts/how-to-hide-content/).
This technique is so cool! Thank you so much for sharing this! I literally yelled out “oh snap!” when the tables changed when the screen width was reduced. :) Can’t wait to share this with my coworkers.
@Paul: I’m glad it was helpful to you.
@Sean: Thanks for the heads-up! I will update the Gist and demo accordingly.
@Megan: Awesome! Let me know how it goes.
Aren’t we supposed to be making pages lighter? Not filling the source up with extra attributes?
nice technique, thanks for share
Really cool technique, but is it only possible with SASS? Could you do this with just straight CSS?
@John: I hear where you’re coming from. Personally, I think one of the benefits of a web standards-based approach is lighter pages overall. Less markup, less presentational nonsense, etc. This reduction in bloat then frees us up to enhance the page with markup that improves the experience for users. For me, traditionally, that has meant microformats, but I think it extends to
data-*
attributes as well. We usedata-*
attributes to provide instructions for JS and (as seen here) to increase the flexibility of the content. I think it’s a fair trade-off, but if I did not think the additional code was adding anything to the document, I would not put it in there (instead perhaps injecting via JS).@Guario: Absolutely! I just happened to have the SASS code handy as we’ve been using SASS for pretty much everything of late. Taking a look at the rendered preview in Codepen should give you the final CSS rules.
Smart and efficient coding can minimize this load time. Sometimes people mistake responsive Web design for “time-saving” development, but in reality, coding responsive websites correctly is hard. Minimizing load time requires expertise in coding and a solid knowledge of how code and browsers work.
Nice one, thanks!