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 naturally responsive. Chris Coyier has a great write up on CSS-Tricks regarding responsive data tables, and although this addresses most issues, there are a couple that it doesn’t. Primarily, overlapping text on small screens and getting labels to stack vertically when they don’t fit horizontally.

It’s important to note that this isn’t a one-size-fits-all type of solution. Like responsive design in general, you have to base your media queries on your content; find out where your tables start to get messy and set your breakpoints accordingly. 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 difference to the CSS-Tricks article linked above is that I don’t absolutely position the data labels (the td:before selector), instead I keep them in the flow of the document to ensure that the text won’t overlap. I keep everything aligned by using negative margin to offset the labels into the empty space created by giving the table cells a large amount of padding on their left-hand side. By setting that padding to 50% we can offset the labels using a margin of -100% and avoid having to do any maths.

Another change to the CSS-Tricks article is the use of the second media query to trigger a vertically stacked table in which labels are above the data instead of alongside it. This means that tabular data is still readable, even on devices with small screens.

These are my tricks to getting tables to behave responsibly, what are yours?

Tweet this

9 Comments

  1. Russel

    Can you do a version of this that is mobile-first instead? It seems backwards that we use these mobile-first frameworks (Bootstrap, Foundation, etc.) but we’re always encouraged to write our own styles desktop-first…

  2. Charlie C

    I definitely like this approach to responsive tables. You’re right about it not being a solution 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 version up on CodePen (http://codepen.io/pixelchar/pen/rfuqK) that, at small widths, takes each row, moves it vertically and uses th[scope=”row”] to show it as a header to visually set off each row. Leads to a lot of scrolling, but the data is presented cleanly to the viewer.

    It takes a bit more CSS to do it mobile-first and accessible because you have to undo the default table styling on narrow viewports and then redo them as the browser gets wider, but it can be done and gives a nice presentation.

  3. Josh

    Russel, I’m an advocate of using a mobile-first approach, but tables are often an exception. If you think about how tables are naturally displayed, they are designed for larger screens, and therefore that’s how I code them. Keep in mind that there is nothing wrong with having a predominantly mobile-first codebase with aspects of it that are written specifically for larger screens where relevant.

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

  5. Aaron Gustafson

    Yup. This is (roughly) my recommended approach (demo). Both the BBC and NPR have also used this approach as the basis for their respective responsive table strategies. The one change I’d recommend is to actually hide the header cells using display: none as the generated content can be read out by a screen reader.

    As for the alleged “desktop first”-ness of this approach, that’s not necessarily the case. More often than not, tables make the most sense semantically for the content. True, if you go with a table you need to “undo” the styles below a certain device width, but that’s ok. I’d rather let the browser lay out the table on larger screens and run a few simple “undo” styles in a max-width media query. It’s going to result in a lighter download for the user and requires way less effort as the designer.

  6. Josh

    Aaron Gustafson , regarding your comment on accessibility, as far as I am aware not all assistive technology is able to access the data attribute. Until this is the case, I think it’s wise to keep the header cells accessible to screen readers.

  7. Meko

    Hey, I figured I’d pitch an alternative to your solution. It uses a similar principle of dropping the position: absolute; and using a negative margin to position the labels instead. The difference being that rather than rely on knowing when to break it into a stacking version I tried to make it work in it’s two column format after the initial switch.

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