Getting Responsive Tables to Behave

Getting Responsive Tables to Behave

View an example

This post will take 1 minute to read.

Tables can be tricky to work with and they aren’t nat­ur­ally respons­ive. Chris Coyier has a great write up on CSS-Tricks regard­ing respons­ive data tables, and although this addresses most issues, there are a couple that it doesn’t. Primarily, over­lap­ping text on small screens and get­ting labels to stack ver­tic­ally when they don’t fit hori­zont­ally.

It’s import­ant to note that this isn’t a one-size-fits-all type of solu­tion. Like respons­ive design in gen­er­al, you have to base your media quer­ies on your con­tent; find out where your tables start to get messy and set your break­points accord­ingly. Here’s the markup for an example table and the base CSS that I use to ensure that the table works across all device sizes:

<table>
    <thead>
      <tr>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Hero Title</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td data-label ="First Name">Bruce</td>
        <td data-label ="Last Name">Wayne</td>
        <td data-label ="Hero Title">Batman</td>
      </tr>
      <tr>
        <td data-label ="First Name">Peter</td>
        <td data-label ="Last Name">Parker</td>
        <td data-label ="Hero Title">Spiderman</td>
      </tr>
      <tr>
        <td data-label ="First Name">Bruce</td>
        <td data-label ="Last Name">Banner</td>
        <td data-label ="Hero Title">The Hulk</td>
      </tr>
      <tr>
        <td data-label ="First Name">Clark</td>
        <td data-label ="Last Name">Kent</td>
        <td data-label ="Hero Title">Superman</td>
      </tr>
    </tbody>
</table>

table {
  border-collapse: collapse;
  width: 100%;
}
/* Stack rows vertically on small screens */
@media (max-width: first-break-point) {
  /* Hide column labels */
  thead tr {
    position: absolute;
    top: -9999em;
    left: -9999em;
  }
  /* Leave a space between table rows */
  tr + tr {
    margin-top: 1.5em;
  }
  /* Get table cells to act like rows */
  tr,
  td {
    display: block;
  }
  /* Leave a space for data labels */
  td {
    padding-left: 50%;
  }
  /* Add data labels */
  td:before {
    content: attr(data-label);
    display: inline-block;
    margin-left: -100%;
    width: 100%;
  }
}
/* Stack labels vertically on smaller screens */
@media (max-width: second-break-point) {
  td {
    padding-left: 0;
  }
  td:before {
    display: block;
    margin-left: 0;
  }
}

The main dif­fer­ence to the CSS-Tricks art­icle linked above is that I don’t abso­lutely pos­i­tion the data labels (the td:before select­or), instead I keep them in the flow of the doc­u­ment to ensure that the text won’t over­lap. I keep everything aligned by using neg­at­ive mar­gin to off­set the labels into the empty space cre­ated by giv­ing the table cells a large amount of pad­ding on their left-hand side. By set­ting that pad­ding to 50% we can off­set the labels using a mar­gin of -100% and avoid hav­ing to do any maths.

Another change to the CSS-Tricks art­icle is the use of the second media query to trig­ger a ver­tic­ally stacked table in which labels are above the data instead of along­side it. This means that tab­u­lar data is still read­able, even on devices with small screens.

These are my tricks to get­ting tables to behave respons­ibly, what are yours?

Tweet this

9 Comments

  1. Russel

    Can you do a ver­sion of this that is mobile-first instead? It seems back­wards that we use these mobile-first frame­works (Bootstrap, Foundation, etc.) but we’re always encour­aged to write our own styles desktop-first…

  2. Charlie C

    I def­in­itely like this approach to respons­ive tables. You’re right about it not being a solu­tion for every table. Tables with rows that can almost be viewed as a table by itself based on the data.

    I put a mobile-first ver­sion up on CodePen (http://codepen.io/pixelchar/pen/rfuqK) that, at small widths, takes each row, moves it ver­tic­ally and uses th[scope=“row”] to show it as a head­er to visu­ally set off each row. Leads to a lot of scrolling, but the data is presen­ted cleanly to the view­er.

    It takes a bit more CSS to do it mobile-first and access­ible because you have to undo the default table styl­ing on nar­row view­ports and then redo them as the browser gets wider, but it can be done and gives a nice present­a­tion.

  3. Josh

    Russel, I’m an advoc­ate of using a mobile-first approach, but tables are often an excep­tion. If you think about how tables are nat­ur­ally dis­played, they are designed for lar­ger screens, and there­fore that’s how I code them. Keep in mind that there is noth­ing wrong with hav­ing a pre­dom­in­antly mobile-first code­base with aspects of it that are writ­ten spe­cific­ally for lar­ger screens where rel­ev­ant.

  4. Josh

    Charlie, nice work with your mobile-first approach. It shows off the power of CSS really well. And for those that want a mobile-first approach, this is a great solu­tion.

  5. Aaron Gustafson

    Yup. This is (roughly) my recom­men­ded approach (demo). Both the BBC and NPR have also used this approach as the basis for their respect­ive respons­ive table strategies. The one change I’d recom­mend is to actu­ally hide the head­er cells using display: none as the gen­er­ated con­tent can be read out by a screen read­er.

    As for the alleged “desktop first”-ness of this approach, that’s not neces­sar­ily the case. More often than not, tables make the most sense semantic­ally for the con­tent. True, if you go with a table you need to “undo” the styles below a cer­tain device width, but that’s ok. I’d rather let the browser lay out the table on lar­ger screens and run a few simple “undo” styles in a max-width media query. It’s going to res­ult in a light­er down­load for the user and requires way less effort as the design­er.

  6. Josh

    Aaron Gustafson , regard­ing your com­ment on access­ib­il­ity, as far as I am aware not all assist­ive tech­no­logy is able to access the data attrib­ute. Until this is the case, I think it’s wise to keep the head­er cells access­ible to screen read­ers.

  7. Meko

    Hey, I figured I’d pitch an altern­at­ive to your solu­tion. It uses a sim­il­ar prin­ciple of drop­ping the pos­i­tion: abso­lute; and using a neg­at­ive mar­gin to pos­i­tion the labels instead. The dif­fer­ence being that rather than rely on know­ing when to break it into a stack­ing ver­sion I tried to make it work in it’s two column format after the ini­tial switch.

    http://codepen.io/thepinkfox/pen/PPKQed