Data tables don’t do so well with responsive design. Just sayin’.
He has a good point. Data tables can be quite wide, and necessarily so. A single row of data needs to be kept together to make any sense in a table. Tables can flex in width, but they can only get so narrow before they start wrapping cells contents uncomfortably or just plain can’t get any narrower.
Responsive design is all about adjusting designs to accommodate screens of different sizes. So what happens when a screen is narrower than the minimum width of a data table? You can zoom out and see the whole table, but the text size will be too small to read. Or you can zoom into the point of readability, but browsing the table will require both vertical and (sad face) horizontal scrolling.
So here’s what we are gonna do…
We’re going to use “responsive design” principles (CSS @media queries) to detect if the screen is smaller than the maximum squishitude of our table. If it is, we’re going to reformat the table.
We’re being good little developers and using Plain Ol’ Semantic Markup here for our table. Bare bones example:
<table>
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Job Title</th>
</tr>
</thead>
<tbody>
<tr>
<td>James</td>
<td>Matman</td>
<td>Chief Sandwich Eater</td>
</tr>
<tr>
<td>The</td>
<td>Tick</td>
<td>Crimefighter Sorta</td>
</tr>
</tbody>
</table>
Our regular CSS is nothing special:
/*
Generic Styling, for Desktops/Laptops
*/
table {
width: 100%;
border-collapse: collapse;
}
/* Zebra striping */
tr:nth-of-type(odd) {
background: #eee;
}
th {
background: #333;
color: white;
font-weight: bold;
}
td, th {
padding: 6px;
border: 1px solid #ccc;
text-align: left;
}
The small-screen responsive stuff comes in now. We’ve already figured out our minimum table width is about 760px so we’ll set up our media query to take effect when the narrower than that. Also, we’ll target iPads as they are right in that zone.
The biggest change is that we are going to force the table to not behave like a table by setting every table-related element to be block-level. Then by keeping the zebra striping we originally added, it’s kind of like each table row becomes a table in itself, but only as wide as the screen. No more horizontal scrolling! Then for each “cell”, we’ll use CSS generated content (:before
) to apply the label, so we know what each bit of data means.
/*
Max width before this PARTICULAR table gets nasty
This query will take effect for any screen smaller than 760px
and also iPads specifically.
*/
@media
only screen and (max-width: 760px),
(min-device-width: 768px) and (max-device-width: 1024px) {
/* Force table to not be like tables anymore */
table, thead, tbody, th, td, tr {
display: block;
}
/* Hide table headers (but not display: none;, for accessibility) */
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr { border: 1px solid #ccc; }
td {
/* Behave like a "row" */
border: none;
border-bottom: 1px solid #eee;
position: relative;
padding-left: 50%;
}
td:before {
/* Now like a table header */
position: absolute;
/* Top/left values mimic padding */
top: 6px;
left: 6px;
width: 45%;
padding-right: 10px;
white-space: nowrap;
}
/*
Label the data
*/
td:nth-of-type(1):before { content: "First Name"; }
td:nth-of-type(2):before { content: "Last Name"; }
td:nth-of-type(3):before { content: "Job Title"; }
td:nth-of-type(4):before { content: "Favorite Color"; }
td:nth-of-type(5):before { content: "Wars of Trek?"; }
td:nth-of-type(6):before { content: "Secret Alias"; }
td:nth-of-type(7):before { content: "Date of Birth"; }
td:nth-of-type(8):before { content: "Dream Vacation City"; }
td:nth-of-type(9):before { content: "GPA"; }
td:nth-of-type(10):before { content: "Arbitrary Data"; }
}
And so, desktops get the regular table experience, mobile (or otherwise small screens) get a reformatted and easier to explore table:
Hey what about IE?
IE 9 and down don’t like you setting table elements as display: block;
It does weird stuff and doesn’t work right. But IE 9 does support media queries. So my solution thus far is just to wrap the media query styles in conditional comments.
<!--[if !IE]><!-->
<style>
/* table-related media query stuff only */
</style>
/* Or an external stylesheet or whatever */
<!--<![endif]-->
If the problem was only that older versions of IE don’t support media queries, we could use the css3-mediaqueries-js project (polyfills support for all media queries) or Respond (also polyfill, way smaller, but only does min/max-width). They both work very well. But this isn’t our problem in this case.
This all works fine in IE 10 and IE 10 also ignores conditional comments, so the styles will work even if wrapped in !IE conditionals.
See it
There are two pages to the demo, one the responsive table solution, and a link to the non-responsive version so you can jump back and forth to see the problem.
In the demo, I use a couple of extra media queries for mobile to force the body to certain widths so they don’t get feisty. View source to snag.
This isn’t perfect…
This is just one potential solution to the problem to data tables on small screens. It’s not perfect. There may be some accessibility concerns (or maybe not, I’m really not sure). It’s likely there are some fancy JavaScript solutions that could approach things differently and also work great. If other solutions to this come along, I’ll keep this page updated.
UPDATE: Other ideas
Scott Jehl created two alternative ideas that are both very cool and very different from this. This highlights an important point: it’s all about the specific context of your data table that dictates what solution to go with.
One of them makes a pie graph from the data in the chart. On narrower screens, the pie graph shows and the table hides, otherwise only the more information-rich table shows. Arguably, the mobile version is more useful!
The next idea (Scott credits Todd Parker) is to turn the table into a mini graphic of a table on narrow screens, rather than show the whole thing. This shows the user there is a data table here to be seen, but doesn’t interfere with the content much. Click the table, get taken to a special screen for viewing the table only, and click to get back.
Mobile First version by Derek Pennycuff.
Version that starts with divs and the labels are generated by pseudo elements and data-attributes by Mobifreaks.
Just hide non-essential stuff version by Stewart Curry
Generate mobile versions directly (without the div’s stuff) from Hannes Kirsman based on this Gist (live demo).
Very slick.
I find it ironic that we are now using CSS to format tables, rather then using tables to format whole entire pages.
its not ironic at all.
Tables are/were designed purely for tabular data organized into neat rows and columns. Pages are not nearly as rigid in their design, therefore tables apply rigid layout principles to a flexible design medium and that is a BAD idea.
CSS was designed to style all of the elements in a page, tables included, so its perfect for its intended function.
~M
That fits my definition of ironic
Really great example. I was initially a bit concerned about how it’d come out, but it looks great.
I do have a question, though. I thought HTML was for (marked up) content and CSS was for style. Should there be a concern that you’ve now added content to your CSS, in the form of labels?
Isn’t this as big a sin as adding style to your HTML?
It might be a gray area…
If you were doing this:
That’s certainly sinful.
But in this case, the markup STILL HAS proper table headers, so it’s as accessible as any other data table in any other environment. We’re just hiding the headers in one place and showing them in another without changing markup.
Still, like I said, could be a gray area, I just don’t know.
Gotcha, thanks!
You can try to use “data-” attribute, to which pseudo-elements have access through content declaration:
content: attr(data-something);
Also you can give any table cell headers attribute, which is super semantic and accessible, and use them instead of data. But there is small problem – headers contains values of corresponding headers IDs. I’m not sure, if setting those ID in space separated words would be considered as a good practice (every word would be independent ID and you can pretty fast run out of ideas for different words, cause ID must be unique).
What a wonderfully elegant solution! Thanks for sharing.
Nice solution for the problem of non-readable tables on small screens. Thanks for sharing!
Idea from @chriseppstein:
Use data attributes for the cells, so content stays in HTML not in CSS:
Then CSS is more like:
Clean. Kinda microformatty.
Nice, thanks for following up on that Chris!
I’d recommend using the axis attribute since that’s supposed to define to which headers a cell’s data belongs.
This is great for multi-language websites!
Hey, I am applying this kind of responsive style to magento and I want it to propagate to all of the tables everywhere so I wrote a piece of jQuery code that will fill the data-heading(thats my attribute) with the appropriate info:
Requirements:
1. Add the class “data-table” to your data tables..
2. Use a proper thead/tbody structure
3. Apply the needed CSS for the data-heading attribute as described
Hope this helps someone.
Cheers
From @mathias — We may not need to use “only screen” as part of the media query. This stuff might apply well in other media as well, although I haven’t tested it. I’m more comfortable leaving it as screen only until I can see this being useful ON those other media.
Nice solution.
Really useful. Great Tip Chris.
P.S. I am sharing with my friends right away.
Good implementation, but a poor solution.
It’s totally unreadable as a table now. I think i’d rather scroll / turn my phone into landscape mode…
I agree but then it could depend on the table and the user’s familiarity with it.
combine this with the “next” “other” idea (the “Tap to View” model), and I think it’s a winner. You have accessible tabular information which is kept out-of-the way of the layout until called for.
Having worked with data tables in responsive designs before I know they can be a pain.
My concern with this example is that we only see the data table. Reformatting the data to fit onto a narrow screen is great in the context of this example but in the context of an entire site it can consume a lot of space and easily leave a user lost.
Fortunately there is kind of something we can do about this. Admittedly it’s not the best solution but if we want to maintain the horizontal aspect of our data table we can use overflows.
For multi-touch devices such as the iPhone and iPad you can nest your table in a div and set the div overflow to auto. As your flexible site contracts around the table the overflow property generates a scrollbar. Your iPhone or iPad wont display the scrollbar but you can now use 2 fingers to swipe the table left or right in order to display all of the data.
For those devices not fortunate enough to enjoy multi-touch Javascript can be utilized to swipe back and forth with a single touch within this div. Then again this isn’t javascript-tricks.com thus I maintain this is an incomplete solution yet a different angle on the problem.
Love this!! Thanks
Two iPhones with a comment: “Both equally suck”. Priceless :D. I’ll actually read the article when I stop laughing :D
This, my friend, is quite awesome!
Although I agree with the previous commentors who properly pointed out, that this solution might heavily depend on the kind of information inside the table.
Very nice example here. On the adding in content with CSS issue.
In the same way you’ve hidden the elements, couldn’t you have add an extra markup and hide it while in the standard layout?
Would this make your markup not semantic since you’d have repeated data?
Rich
One of the best solution around. Im sure it will be enhanced more in the future.
wow!!! good one as usual. cheers mate.
wow, this is elegant and creative… love how it works out
Wow! this is really great!!
But, would have been better if we could retain the heading bgs in the resized window as well!!
I don’t know if I agree that this is the most elegant solution. That is a LOT of vertical scrolling just to see a little data with all the visual association of a row gone. I would find that a massive pain to read.
I’ve been pondering this exact dilemma for a project I’m working on recently myself. The solution I settled on was to have a containing div set to overflow scroll. That way there is an easily visible horizontal scrollbar that does not impact the rest of the page but allows the data to retain its formatting.
10x for sharing this!
Now I want to try and find a solution :) Its a good topic, one that hasn’t really been covered yet. I wonder other solutions people will come up with
This is one of the more difficult things I’ve had to deal with lately and I’m not sure there’s a great answer quite yet.
What I did was wrapped the table in a div and set it to overflow:auto so you get a side-scroll when on a small screen, then hook up some touch detection so you can swipe/scroll the table.
Also, not the greatest way to go about it but, yea tough question to tackle.
Another approach that requires no JavaScript and no labeling (manual or scripted) is to use rainbow striping on the columns in conjunction with different-colored zebra striping on the rows.
I wrote up a pure CSS example and have a barebones working demo available if you’re interested.
Speaking as someone who doesn’t do web-dev for a living, and just likes to use proper CSS for my personal-use webpages: regardless of whether or not this is a good solution, it’s frankly awesome you can do it just with CSS.
Just posting a comment to add to your comment graph. ;-)
Wow, thanks for this! I found it really creative & helpful.
Spoon! This is a clever way to change to the every growing trend of having to view things in multi viewing formats. Once my local area gets good enough cell signal it will become more important. Yeah there are still areas in the US that do not get 4G. Come visit northern Michigan sometime.
Cheers and keep up the good work.
Kevin “Mistfit” Mist
You said “squishitude” this was a great moment.
Thanks for insight Chris, always useful. Your solution perfectly viable, pie chart equally as good. What was the other one, ah click to view table page. Could also list the titles and do a press to bring corresponding row of data into view.
All good. Cheers.
Working on a similar solution now that is looking promising that uses Chris Eppstein’s data-label solution and a potential IE fix using float:left;
It is possible to get the same layout to work even for IE8 and IE9 by adjusting the following within the media query:
table, thead, tbody, th, td, tr{
display: block;
width:100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
float:left;
clear:left;
}
and then for each
td
within the media query setting a height, for example:40px
, and also usingrespond.js
to get media queries to work with IE8 :)The irony is using hand-coded CSS to specify semantic content in the :before pseudo-elements, in a manner that is incidentally not scriptable and therefore cannot be replicated proceduraly.
But the technique is pretty far sighted as far as the styles go and indicates to a large extent how form-factor-centric data markup needs to be. Ultimately this should be the result of individually selectable views for the data in question: the conversion of table to individually labelled key:value pairs. A structure may be desirable.
On the subject, a friend recently asked me how, as an information designer and front-ender, I would engineer complex automated test reports tables to display well on his smartphone when he was away from work. The answer I gave cited a more granular approach: either offer manual constraints on the scope of displayed data via queries or scripting, or offer a scripted interface which presents a very low resolution display that can be quickly expanded to offer the details of specific entries.
Sometimes the nature of the data, and its meaningfully desirable forms, mean that a small-screen display cannot adequately present it without doing a disservice to the data: sometimes, extra user effort is necessary for the data to retain ostensible relevance.
Nice! Where I work, we struggle with programmatically producing tables for print, often too wide. Something like this might apply for us. Great inspiration. Definitely agree that the format depends on the data and what you know about the reader. For example, in many contexts it would be perfectly understandable to skip the “first name”, “last name” labels, and just put the human-readable name at the top of the cell. Turn it into a kind of card view.
What happens when you have two tables on the same page? How do you apply it to just one table?
Never mind it was answered above (i.e. data attributes)
Awesome post.
Is we addressing your audience? I think girls write CSS too.
That’s fair, I’ll change it.
I think this is for only cases when the table occupies the whole width of the page.
What about when the table is just on side of the page.(Ex: a div on left side occupying 40% width and our table on right side with 60%.)
I used this in the above case. Its failing in low-resolution screens.
How to solve this ….?
It was awesome example . but what if we have two tables in a single page.
td:nth-of-type(1):before { content: “label for the first row of the first table”; }
i think there is no nth-of-class sort of think . so please reply how to accomplish this for more than one table in a single page
How about displaying less column and switch the column to be displayed by dragging horizontally (right/left) ?
(@sufail : uses an id selector for you different table)
hi Chris Coyier… im your fan. L) This is really good… i feel very happy :) Thanks.
I’ve implemented this for a shopping cart, the ‘position: absolute’ seems to stop the ‘quantity input’ from being changed. eg. shopper choices larger quantity and then ‘updates’ cart.
When I remove the position: absolute the products and labels center, and I want it left aligned. Left aligned doesn’t work.
Any idea on what else I can do would be much appreciated.
This is only for the mobile phone the desktop size is fine.
Cheers, it saved my Monday !
I’ve solved my problem.
So for anyone else who misses this, here’s what it was:
table td {
/* Behave like a “row” /
border: none;
/ border-bottom: 6px solid green; */
position: relative;
padding-left: 50%; (default 50%, I changed to 30%)
}
So I changed the td to 30% and the td:before to 24%… when I put a background colour on the td: before it showed the issue plain as day.
Just kept missing something so simple.
Hope this helps someone else, I like to understand how things are working, rather than blindly copy/paste so pleased I found it.
This does not work for me, I have no idea why, spent around 14 hours trying to get it to work, cleared all styles and tried again, again, and again. I am not sure what’s causing weird problems with my tables.
This is wonderful! It doesn’t work in IE though.
I think the solution is just partway there. Works OK for this table but isn’t as considerate for future dev.
I’d like to see a combination of Derek’s table but with locked headers like we do in Excel, so that the labels stay on the page and only displays one td result at a time. [Or maybe in a combination with Overflow for the results]
Here’s one example of a floating TH kinda;
http://jsfiddle.net/FyJwZ/7/
Is it possible to get the table headings on their own line so that the content does not overlap on small screen sizes?
I have no experience in CSS, but could serve http://cssdesk.com/GRBqd/ start this project inspired by http://jsfiddle.net/DHjVE/
Is there a way to have the pseudo-labels only appear for cells that have content? I am doing a simple attendance list and my three columns are Host | Spouse | Guests. To keep the scrolling to a minimum, it would be great if the pseudo-labels did not show unless there is data.
I am pretty new to CSS but my understanding is that there is very little conditional logic that you are able to do within it.
I have 2 different charts on one page that are relational. How do i get the before td correct fo each chart? I have tried classes with different elements but it always leaves the 1st td blank.
Beautiful handy solution with minimal effort.
I’m having issues with this in a jqueryui page though.
Works fine alone: Your text to link here…
but doesn’t respond well here: Your text to link here…
Thoughts anyone?
I like how this reformats the table. I have several problems with it. The first is using table tags (I much prefer divs). The second is using this with dynamic pages so that all tables get formatted like this. The third is putting strings into CSS is just horrible for multi-lingual sites.
Has anyone tried using hidden elements for the column titles? Has anyone tried using divs for the structure?
A workaround for IE9 support – just float the table rows and cells:
tr, td {
display: block;
float: left;
}
What’s the reason for the
padding-right: 10px;
in thetd:before
? As far as I can tell this doesn’t do anything. Am I missing something?table, thead, tbody, th, td, tr{
display: block;
width:100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
float:left;
clear:left;
}
This code here does the trick for my IE9 problem (Thanks to Cristoffer), but IE9 crashes every time a try to restore my browser’s screen to its original size. Anyone got an idea why?
nice script – but any idea why it does not work in Windows phone?
This worked great, Chris. Best solution out there!
This worked like a charm!! Thank you so very much!
For the time being my site was done with wordpress while I hand code it.
Is there a way to keep the text-wrap working in the td once it switches to smaller screens? I’m getting long text stretching outside the table and a scrollbar after the switch. I’ve tried various word-wrap additions without success.
In your demo, dream vacation city and city name, text’s are getting overlapped when browser in minimized horizontally. How to prevent this?