Responsive Tables

It’s not unusual to have data which suits being displayed in a tabular format. In the web world we have for this the table element, a classical feature of HTML, and once upon a time also a misused staple of layout. Today we have far better tools for layout, but there remains a challenge with tables: how to display them as the screen-width shrinks, or to use the current parlance, how to make them responsive.

The internet includes a number of options on how best to handle narrow tables, including hiding columns and using horizontal scroll. However I’m operating under the assumption that all the table data must still be displayed, and it should remain grouped by its data rows (which may or may not be rendered horizontally).

I considered three general approaches to a responsive tabular layout: what I will call div-flex; CSS grids; and tables.

In div-flex, each data row is rendered as a div with display: flex, and each data element is a block element within. As the screen-width reduces, the flex layout will wrap the elements onto subsequent lines. The big drawback with this approach is that all the block elements need to be sized consistently, and as it wraps it loses the context of any header row.

In CSS grids each data element is a block element, but unlike div-flex, all those elements are direct children of some container, i.e. there is no element representing the data row. As the screen-width decreases, media selectors are used to change the shape of the grid so it gradually moves from, say, 6 columns to 1 column as the page width shrinks. The biggest drawback of CSS grids is that they have no row grouping – no way of indicating various blocks belong together as the size changes. They simply aren’t designed to have a notion of row-association.

Finally we return to the table. It is semantically the preferred choice and there is a good solution out there which changes the table into a card layout when the media-width is reached. However the drawback with that solution is the need to specify column headers in CSS, something that at best separates content from layout (HTML is for content, not CSS) and, if the headers are dynamically set, is impractical.

My change is to create a small amount of content duplication as a penalty: given a table with headers, we add the header information into a hidden field in each td that is only visible once the screen width is suitably reduced. This is a lot of duplication, but most of us are using HTML generated by templates which support looping, so it’s very little extra effort on our part. The resulting HTML will look something like this (using Angular syntax):

<table class="table-reponsive">
  <thead>
    <tr>
      <th>Name</th>
      <th>Color</th>
      <th>Size</th>
    </tr>
  </thead>
  <tr *ngFor="let shirt of shirts">
    <td><span class="hidden-label">Name</span>{{shirt.name}}</td>
	<td><span class="hidden-label">Color</span>{{shirt.color}}</td>
	<td><span class="hidden-label">Size</span>{{shirt.size}}</td>
  </tr>
</table>

And the CSS:

table.table-reponsive {

    td > span.hidden-label { display: none; } /* Hide labels */

    /* based on https://css-tricks.com/responsive-data-tables/ */
    @media (max-width: 768px) {

        table, thead, tbody, th, td, tr { display: block; }
        thead tr { position: absolute; top: -9999px; left: -9999px; } /* Hide table headers */       
        tr { border: 1px solid #ccc }
        td { border: 0 }
        td > span.hidden-label { display: inline-block; width: 7em; }
    }
}

The result is that the table layout will change to a card layout at width 768px, and display each data-row as a card with the header and the data value.