Grow your CSS skills. Land your dream job.

Responsive Data Tables

Published by Chris Coyier

In addition to the techniques below, see this roundup of additional explorations of this problem.

Garrett Dimon:

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 accomodate 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 in to 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: "Porn Name"; }
	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.

View Demo   Download Files

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!

View Demo

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.

View Demo

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).

Comments

  1. Jesse
    Permalink to comment#

    Very slick.

  2. Bradley Rosenfeld
    Permalink to comment#

    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

    • Stomme poes
      Permalink to comment#

      That fits my definition of ironic

  3. What a wonderfully elegant solution! Thanks for sharing.

  4. Nice solution for the problem of non-readable tables on small screens. Thanks for sharing!

  5. Idea from @chriseppstein:

    Use data attributes for the cells, so content stays in HTML not in CSS:

    <td data-label="First name">Chris</td>

    Then CSS is more like:

    td:before { content: attr(data-label); }

    Clean. Kinda microformatty.

    • Dave
      Permalink to comment#

      Nice, thanks for following up on that Chris!

    • Jon Wolski
      Permalink to comment#

      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:

      jQuery('.data-table').each(function() {
          var thetable=jQuery(this);
          jQuery(this).find('tbody td').each(function() {
              jQuery(this).attr('data-heading',thetable.find('thead th:nth-child('+(jQuery(this).index()+1)+')').text());
          });
      });
      

      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

  6. 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.

  7. Permalink to comment#

    Nice solution.

    Really useful. Great Tip Chris.

    P.S. I am sharing with my friends right away.

  8. Aaron
    Permalink to comment#

    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…

    • Gary
      Permalink to comment#

      I agree but then it could depend on the table and the user’s familiarity with it.

    • Permalink to comment#

      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.

  9. Love this!! Thanks

  10. Permalink to comment#

    Two iPhones with a comment: “Both equally suck”. Priceless :D. I’ll actually read the article when I stop laughing :D

  11. Permalink to comment#

    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.

  12. 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

  13. One of the best solution around. Im sure it will be enhanced more in the future.

  14. ibura
    Permalink to comment#

    wow!!! good one as usual. cheers mate.

  15. wow, this is elegant and creative… love how it works out

  16. Wow! this is really great!!
    But, would have been better if we could retain the heading bgs in the resized window as well!!

  17. Ryan Dunn
    Permalink to comment#

    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.

  18. 10x for sharing this!

  19. 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

  20. Chris
    Permalink to comment#

    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.

  21. Just posting a comment to add to your comment graph. ;-)

  22. Wow, thanks for this! I found it really creative & helpful.

  23. 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

  24. 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.

  25. 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;

    • Permalink to comment#

      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 using respond.js to get media queries to work with IE8 :)

  26. Barney
    Permalink to comment#

    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.

  27. Brian Doherty
    Permalink to comment#

    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.

  28. James B
    Permalink to comment#

    What happens when you have two tables on the same page? How do you apply it to just one table?

  29. Awesome post.

    We are being good boys and…

    Is we addressing your audience? I think girls write CSS too.

  30. N@idu
    Permalink to comment#

    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 ….?

  31. sufail
    Permalink to comment#

    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

  32. Antoine B.
    Permalink to comment#

    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)

  33. saro
    Permalink to comment#

    hi Chris Coyier… im your fan. L) This is really good… i feel very happy :) Thanks.

  34. Vickis
    Permalink to comment#

    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.

  35. Permalink to comment#

    Cheers, it saved my Monday !

  36. Vickis
    Permalink to comment#

    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%)
    }

    table td:before { 
        /* Now like a table header */
        position: absolute;
        /* Top/left values mimic padding */
        top: 6px;
        left: 6px;
        width: 45%; (45% is the default, I changed to 25% which made the - td:nth-of-type(1):before - overlap the input)
        padding-right: 10px; 
        white-space: nowrap;
    }
    

    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.

  37. Permalink to comment#

    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.

  38. miaj
    Permalink to comment#

    This is wonderful! It doesn’t work in IE though.

  39. Colin Leger
    Permalink to comment#

    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]

    Derek; Mobile First version by Pennycuff. http://jsbin.com/arixic

    Overflow; http://css-tricks.com/examples/OverflowExample/

  40. Brian
    Permalink to comment#

    Is it possible to get the table headings on their own line so that the content does not overlap on small screen sizes?

  41. CMiguel
    Permalink to comment#

    I have no experience in CSS, but could serve http://cssdesk.com/GRBqd/ start this project inspired by http://jsfiddle.net/DHjVE/

  42. MarcBF
    Permalink to comment#

    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.

  43. Pat
    Permalink to comment#

    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.

  44. 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?

  45. 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?

  46. John Pitcairn
    Permalink to comment#

    A workaround for IE9 support – just float the table rows and cells:

    tr, td {
    display: block;
    float: left;
    }

  47. Neil Haskins
    Permalink to comment#

    What’s the reason for the padding-right: 10px; in the td:before? As far as I can tell this doesn’t do anything. Am I missing something?

  48. Roy
    Permalink to comment#

    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?

  49. steve
    Permalink to comment#

    nice script – but any idea why it does not work in Windows phone?

  50. This worked great, Chris. Best solution out there!

  51. Adrea

    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.

  52. Aaron
    Permalink to comment#

    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.

  53. Anant
    Permalink to comment#

    In your demo, dream vacation city and city name, text’s are getting overlapped when browser in minimized horizontally. How to prevent this?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".