Responsive tables using CSS Grid Layout

2018-09-09

Tables can be a big problem on mobile devices. There are a number of articles covering the subject of how to make them responsive all with their own drawbacks.

I realised recently that the new CSS Grid Layout could be used to solve this and make them look like a lot of mobile apps choose to display data like that. At least for tables that are not huge but still too big for mobile devices. I think four to eight columns would be optimal to make this work.

This idea of using grid to make tables mobile friendly came to me while using my online bank on my phone and getting irritated that it isn’t responsive. Then I thought that my bank probably has a lot of legacy code and didn’t want to make big changes. So my goal in this article is to make minimal changes to show how easy it can be with CSS Grid.

Minor markup changes before we get started

The markup I am going to work with in this article is pretty much the same as my banks. However I did add some classes and renamed others. Also in the "Account" column there was no markup separating the account name from the account number so I took the liberty to add tags and classes to them. I could have worked around those things but I wanted to clean it up a bit for this article . Here is the markup I will be working with. A pretty simple table:

<div class="table__wrap">
  <table class="table">
    <thead class="table__header">
      <tr class="table__row">
        <th class="table__cell u-text-left">Account</th>
        <th class="table__cell u-text-right">Balance</th>
        <th class="table__cell u-text-right">Limit</th>
        <th class="table__cell u-text-right">Available</th>
        <th></th>
      </tr>
    </thead>

    <tbody>

      <tr class="table__row">
        <td class="table__account table__cell">
          <a href="#" class="table__account-content table__link table__link"><span class="table__account-number">1110-26-000487</span> <span class="table__account-name">Checking Account</span>
          </a>
        </td>
        <td class="table__balance table__cell u-text-right u-font-mono"><span class="num_negative"> -250.000 </span></td>
        <td class="table__limit table__cell u-text-right u-font-mono">500.000</td>
        <td class="table__available table__cell u-text-right u-font-mono">250.000</td>
        <td class="table__transfer table__cell u-text-center"><a class="btn" href="">Transfer</a></td>
      </tr>

      <tr class="table__row">
        <td class="table__account table__cell">
          <a href="#" class="table__account-content table__link"><span class="table__account-number">1110-26-000487</span> <span class="table__account-name">Savings</span></a>
        </td>
        <td class="table__balance table__cell u-text-right u-font-mono"><span class="num_negative"> 1.000.000 </span></td>
        <td class="table__limit table__cell u-text-right u-font-mono">0</td>
        <td class="table__available table__cell u-text-right u-font-mono">1.000.000</td>
        <td class="table__transfer table__cell u-text-center"><a class="btn" href="">Transfer</a></td>
      </tr>

      <tr class="table__row">
        <td class="table__account table__cell">
          <a href="#" class="table__account-content table__link"><span class="table__account-number">1110-26-000487</span> <span class="table__account-name">Joint Account</span></a>
        </td>
        <td class="table__balance table__cell u-text-right u-font-mono"><span class="num_negative"> 150.000 </span></td>
        <td class="table__limit table__cell u-text-right u-font-mono">0</td>
        <td class="table__available table__cell u-text-right u-font-mono">150.000</td>
        <td class="table__transfer table__cell u-text-center"><a class="btn" href="">Transfer</a></td>
      </tr>

    </tbody>

    <tfoot>

      <tr class="table__row table__row--last">
        <td class="table__cell" align="right">Total (<acronym title="US Dollars">USD</acronym>):</td>
        <td class="table__balance table__cell u-text-right u-font-mono">900.000</td>
        <td class="table__limit table__cell u-text-right u-font-mono">500.000</td>
        <td class="table__available table__available--total table__cell u-text-right u-font-mono">1.400.000</td>
        <td></td>
      </tr>

    </tfoot>
  </table>
</div>

Prioritising the content

First thing I want to do is decide what content is important to a mobile user and what we can let go off. This is just to declutter the interface and set our priorities straight.

The table headers become rather useless to us on mobile since they will not be able to serve their job properly once I change the layout so I will hide them on mobile. For that I use the utility class I created called .u-visually-hidden--mobile that makes sure it's still there for people who use screen readers.

Rows become grids

Adding display: grid to the rows changes each row to a grid. I apply it on the rows so I can stack up some of the columns to save horizontal space.

Next I want two grid rows within each table row. To do that I add grid-template-rows: 1fr 1fr; to the table row class. 1fr means one fraction of the available space. This way both rows should be equal height and balanced.

My goal is to have only 3 columns so I will create a template for the columns as well. The first two will be the most important ones, while the last one consists of only the transfer button so it doesn’t need as much space. Which is why I’ll add grid-template-columns: 2fr 2fr 1fr;, first two columns get 2 fractions of the available space each and the last one only gets one.

@media only screen and (max-width: 650px) {
  .table__row {
    display: grid;
    grid-template-columns: 2fr 2fr 1fr;
    grid-template-rows: 1fr 1fr;
    grid-column-gap: 1rem;
    align-items: center;
  }
}

Stacking the columns

The first column, Account, will stay in its own column. I then stack the account name above the account number and make the account number smaller to emphasise the hierarchy.

@media only screen and (max-width: 650px) {
  .table__account {
    grid-column: 1;
    grid-row: span 2;
    }

  .table__account-content {
    display: flex;
    flex-direction: column;
  }

  .table__account-number {
    order: 2;
    font-size: 12px;
    padding-top: 0.25rem;
  }
}

I want to try to limit the width to two or three columns and since "Limit" is not that important to be present on this screen — And because it mostly shows zeroes — I'll be removing it from display. They would usually be able to see the limit on their account page anyway so it's not a priority for this overview table. I think you can assume that people usually only have a limit on one account and know how much it is.

The "Balance" and "Available" columns are going to get stacked on top of each other with the following code:

@media only screen and (max-width: 650px) {
  .table__balance {
    grid-column: 2;
    grid-row: 2;
    font-size: 0.75rem;
    padding-top: 0.125rem;
  }

  .table__available {
    grid-column: 2;
    grid-row: 1;
    padding-bottom: 0.125rem;
  }
}

They are very related content and stacking them should give you a quick overview. But there is a problem. I got rid of the table headers so there is no indication of what the numbers mean at the moment. To fix that problem I’m adding the following CSS:

@media only screen and (max-width: 650px) {
  .table__balance::before {
    content: "Balance: ";
  }
}

This adds the text “Balance: “ before the balance tag on mobile devices giving a clear distinction between the two values.

Finally I make the transfer button span two rows so it is vertically centered with the other content like so:

@media only screen and (max-width: 650px) {
  .table__transfer {
    grid-column: 3;
    grid-row: span 2;
  }
}

The results

It wasn't very complicated but the results are a neat and simple mobile friendly layout that is easy to use and understand. I've added a few cosmetic styles just to make it look a bit better but what I have shown here above are the important parts and the results are here in this pen:

See the Pen Responsive tables using CSS Grid Layout by Gilli Sigurðs (@gilli) on CodePen.

Why?

Now you may be thinking “But Gilli, why do we use tables at all then? Wouldn't it be simpler to just use CSS grid all the time". Here are some of the reasons to use tables:

Conclusion and caveats

Overall I think CSS Grid Layout is incredibly powerful to transform content completely and adapt it to whatever scenario you need to. I haven't used this solution in a production environment yet so there might be some more problems that I haven't thought of but mostly I haven't see any big ones so far, except for browser support of course. However here are a couple of caveats that you should keep in mind before using this: