Card-Style CSS-Only Responsive Tables

This article describes a CSS-only method for creating card-style responsive tables. Such tables display each row as a card when space is limited. This article also discusses alternative methods, some of which are useful in certain situations.

The Method

Result

BreedLifespanCountry of OriginDescription
Persian Cat12-17 yearsIranThe Persian cat is a long-haired breed of cat characterized by its round face and short muzzle.
Maine Coon10-15 yearsUnited States of AmericaThe Maine Coon is one of the largest domesticated breeds of cat. It has excellent hunting skills.
British Shorthair14-20 yearsGreat BritainThe British Shorthair has a distinctively chunky body, a dense coat, and a broad face.

Adjust your window's width to test the table's responsiveness.

Code

HTML
1<table>
2  <thead>
3    <tr>
4      <th>Breed</th>
5      <th>Lifespan</th>
6      <th>Country of Origin</th>
7      <th class="align-start">Description</th>
8    </tr>
9  </thead>
10  <tbody>
11    <tr>
12      <td data-label="Breed"><span>Persian Cat</span></td>
13      <td data-label="Lifespan"><span>12-17 years</span></td>
14      <td data-label="Country of Origin"><span>Iran</span></td>
15      <td data-label="Description" class="align-start"><span>The Persian cat is a long-haired breed of cat characterized by its round face and short muzzle.</span></td>
16    </tr>
17    <tr>
18      <td data-label="Breed"><span>Maine Coon</span></td>
19      <td data-label="Lifespan"><span>10-15 years</span></td>
20      <td data-label="Country of Origin"><span>United States of America</span></td>
21      <td data-label="Description" class="align-start"><span>The Maine Coon is one of the largest domesticated breeds of cat. It has excellent hunting skills.</span></td>
22    </tr>
23    <tr>
24      <td data-label="Breed"><span>British Shorthair</span></td>
25      <td data-label="Lifespan"><span>14-20 years</span></td>
26      <td data-label="Country of Origin"><span>Great Britain</span></td>
27      <td data-label="Description" class="align-start"><span>The British Shorthair has a distinctively chunky body, a dense coat, and a broad face.</span></td>
28    </tr>
29  </tbody>
30</table>
CSS
1/* Styles for card mode (presentational styles are marked as optional) */
2@media (max-width: 855px) {
3  table {
4    display: block;
5  }
6
7  thead {
8    display: none;
9  }
10
11  tbody {
12    display: table;
13  }
14
15  tr {
16    display: table-row-group;
17  }
18
19    tr:nth-child(odd) {
20      background-color: #f9f9f9; /* optional */
21    }
22
23  td {
24    display: table-row;
25  }
26
27    td:before,
28    td > span {
29      display: table-cell;
30      padding: 7px 13px; /* optional */
31      border: 1px solid #e8e8e8; /* optional */
32    }
33
34    td:before {
35      content: attr(data-label);
36      font-weight: 600; /* optional */
37    }
38
39    td:not(.align-start) > span {
40      vertical-align: middle; /* optional */
41    }
42}
43
44/* Styles for normal mode (all are optional, presentational styles) */
45@media (min-width: 856px) {
46  th {
47    white-space: nowrap;
48    font-weight: 600;
49  }
50
51  th, 
52  td {
53    padding: 7px 13px;
54    border: 1px solid #e8e8e8;
55  }
56
57    th:not(.align-start), 
58    td:not(.align-start) {
59      text-align: center;
60    }
61}

HTML Breakdown

Description

The only difference between the HTML for this method and the HTML for a typical table is that contents of <td> elements must be wrapped. For example, in the HTML for this method, the contents of <td> elements are wrapped in <span> elements:

HTML
12      <td data-label="Breed"><span>Persian Cat</span></td>
13      <td data-label="Lifespan"><span>12-17 years</span></td>
14      <td data-label="Country of Origin"><span>Iran</span></td>
15      <td data-label="Description" class="align-start"><span>The Persian cat is a long-haired breed of cat characterized by its round face and short muzzle.</span></td>

<td> elements have the flow content content model, so it is fine for them to have child <span> elements.

Rationale

Wrapping allows the content of a <td> element to be laid out in its own box when the table is in card mode. For example, consider a <td> element in the DOM tree when the table is in card mode:

<td data-label="Breed">
  ::before
  <span>Persian Cat</span>
</td>

This structure allows Persian Cat to be laid out in a box adjacent to the ::before pseudo element's box. This can be done using styles like td:before, td > span { display: inline-block; } or td:before, td > span { display: table-cell; }. If there is no wrapper element, the same <td> element would have the following structure in the DOM tree:

<td data-label="Breed">
  ::before
  Persian Cat
</td>

Persian Cat would be a text node in the same box that contains the ::before element. Hacky solutions would be required to lay Persian Cat out adjacent to the ::before element. Some such solutions are discussed in the alternative methods section.

CSS Breakdown

Description

If the non-optional (non-presentational) styles for this method were inlined, the <table> element would look like this in the DOM tree:

<table style="display: block">
  <thead style="display: none">...</thead>
  <tbody style="display: table">
    <tr style="display: table-row-group">
      <td data-label="label" style="display: table-row">
        ::before <!-- content: attr(data-label); display: table-cell; -->
        <span style="display: table-cell">value</span>
      </td>
      <!-- More <td> elements ommitted for brevity -->
    </tr>
    <!-- More <tr> elements ommitted for brevity -->
  </tbody>
</table>

This is equivalent to a HTML table with the following structure:

<table> <!-- <tbody> element with display: table -->
  <tbody> <!-- <tr> element with display: table-row-group -->
    <tr> <!-- <td> element with display: table-row -->
      <td> <!-- ::before pseudo element with display: table-cell -->
        label
      </td> 
      <td> <!-- <span> element with display: table-cell -->
        value
      </td>
    </tr>
    <!-- More <tr> elements ommitted for brevity -->
  </tbody>
  <!-- More <tbody> elements ommitted for brevity -->
</table>

Rationale

The above-mentioned table structure is a simple and reliable structure for two-column cards. The following are some reasons why:

  • No need to set column widths or row heights, since browsers handle table column widths and row heights.

  • <tbody> elements demarcate cards, allowing for per-card styles, such as alternating background colors.

  • The structure can be made accessible to screen readers through the use of ARIA roles. This is elaborated on in the next section.

Notes

Accessibility

Accessibility is a problem for responsive tables. The root issue is that table elements (<table>, <tr>, <td>, etc) lose their semantic meanings when their display properties are changed. For example, consider the accessibility tree (viewable in Chrome's accessibility pane) of a row in the example table when in normal mode (display properties unchanged):

> table
  > row
    > gridcell "Persian Cat"
    > gridcell "12-17 years"
    > gridcell "Iran"
    > gridcell "The Persian Cat is a..."

The browser correctly interprets semantic meanings, identifying <tr> elements as rows, <td> elements as gridcells and so on. On the other hand, when the table is in card mode (display properties changed), the accessibility tree of the table is just a bunch of GenericContainers.

Unfortunately, all CSS-only, card-style, responsive table methods change display properties. That said, when using this method, JS can be used to improve accessibility by applying ARIA roles. For example, consider the card mode DOM tree with ARIA attributes applied:

<tbody role="table">
  <tr role="rowgroup">
    <td data-label="Breed" role="row">
      ::before
      <span role="cell">Persian Cat</span>
    </td>
    <!-- More <td> elements ommitted for brevity -->
  </tr>
  <!-- More <tr> elements ommitted for brevity -->
</tbody>

The accessibility tree of a row in the table would be as follows:

> table
  > row
    > gridcell "Breed"
    > gridcell "Persian Cat""

Chrome correctly infers the semantic meaning of the ::before element.

Use of Standard Table Elements

For this method, <div> elements coupled with display properties and ARIA attributes can be used in place of table elements. That said, table elements offer some conveniences:

  • No need to specify CSS display properties when in normal mode.

  • No need to specify ARIA roles when in normal mode.

Further Enhancements

  • This method does not consider table elements like <caption>, <col> or <tfoot>, nor does it consider table element attributes like span. If this method is used on tables with such elements and attributes, additional styles are required.

  • If a page relocates elements for responsiveness, EQCSS or ResizeObserver could improve the user experience. For example, this page relocates its side menus to the top of the page when window width decreases past specific breakpoints. Just after a side menu has been relocated to the top of the page, tables are wider than they were immediately before; because of this, having tables to go from normal to card mode when a certain table width is met (using EQCSS or ResizeObserver) instead of when a certain window width is met, could offer a better user experience.

  • The example code for this method uses plain CSS for simplicity's sake. Implementing this method in Sass or Less could provide greater flexibility.

Alternatives

Without Wrappers in <td> Elements

There are situations where wrapping the contents of <td> elements isn't possible. Methods for card-style responsive tables in such situations typically involve taking td:before out of normal flow. The following method uses position: absolute to do just that:

Result

BreedLifespanCountry of OriginDescription
Persian Cat12-17 yearsIranThe Persian cat is a long-haired breed of cat characterized by its round face and short muzzle.
Maine Coon10-15 yearsUnited States of AmericaThe Maine Coon is one of the largest domesticated breeds of cat. It has excellent hunting skills.
British Shorthair14-20 yearsGreat BritainThe British Shorthair has a distinctively chunky body, a dense coat, and a broad face.

The table's responsiveness can be tested by adjusting your window's width.

Code

HTML
1<table>
2  <thead>
3    <tr>
4      <th>Breed</th>
5      <th>Lifespan</th>
6      <th>Country of Origin</th>
7      <th class="align-start">Description</th>
8    </tr>
9  </thead>
10  <tbody>
11    <tr>
12      <td data-label="Breed">Persian Cat</td>
13      <td data-label="Lifespan">12-17 years</td>
14      <td data-label="Country of Origin">Iran</td>
15      <td data-label="Description" class="align-start">The Persian cat is a long-haired breed of cat characterized by its round face and short muzzle.</td>
16    </tr>
17    <tr>
18      <td data-label="Breed">Maine Coon</td>
19      <td data-label="Lifespan">10-15 years</td>
20      <td data-label="Country of Origin">United States of America</td>
21      <td data-label="Description" class="align-start">The Maine Coon is one of the largest domesticated breeds of cat. It has excellent hunting skills.</td>
22    </tr>
23    <tr>
24      <td data-label="Breed">British Shorthair</td>
25      <td data-label="Lifespan">14-20 years</td>
26      <td data-label="Country of Origin">Great Britain</td>
27      <td data-label="Description" class="align-start">The British Shorthair has a distinctively chunky body, a dense coat, and a broad face.</td>
28    </tr>
29  </tbody>
30</table>
CSS
1/* Styles for card mode (presentational styles are marked as optional) */
2@media (max-width: 855px) {
3  table,
4  tbody,
5  tr,
6  td {
7    display: block;
8  }
9
10  table {
11    border-top: 1px solid #e8e8e8; /* optional */
12  }
13
14  thead {
15    display: none;
16  }
17
18  tr:nth-child(odd) {
19    background-color: #f9f9f9; /* optional */
20  }
21
22  td {
23    position: relative;
24    text-align: left;
25    padding: 7px 13px; /* optional */
26    padding-left: calc(50% + 13px); /* 50% is arbitrary, 13px is optional */
27    border: 1px solid #e8e8e8; /* optional */
28    border-top: none; /* optional */
29  }
30
31    td:before {
32      content: attr(data-label);
33      position: absolute;
34      white-space: nowrap;
35      top: 0;
36      bottom: 0;
37      left: 0;
38      width: 50%; /* arbitrary */
39      text-align: left;
40      padding: 7px 13px; /* optional */
41      font-weight: 600; /* optional */
42      border-right: 1px solid #e8e8e8; /* optional */
43    }
44}
45
46/* Styles for normal mode (all are optional, presentational styles) */
47@media (min-width: 856px) {
48  th {
49    white-space: nowrap;
50    font-weight: 600;
51  }
52
53  th,
54  td {
55    padding: 7px 13px;
56    border: 1px solid #e8e8e8;
57  }
58
59    th:not(.align-start),
60    td:not(.align-start) {
61      text-align: center;
62    }
63}

Notes

Arbitrary dimensions are often used for such methods. This causes brittleness. For example, in the method above, td:before has width: 50% so that labels do not wrap and overflow the <td> element. Even then, if a label is longer than 50% of the table's width, wrapping will still occur.

This brittleness makes it difficult to apply such methods across an entire site without making tweaks for each table. For example, like the method above, this method takes td:before out of normal flow, using float: left - as is, it cannot be applied to the example table content used in this article. Nonetheless, such methods are useful when it isn't possible to wrap the contents of <td> elements or when table content is always similar.

With Wrappers in <td> Elements

There alternatives methods to create card-style responsive tables when the contents of <td> elements are wrapped:

  • display: inline-block and display: flex can be used to lay out card rows. The downside to these layout methods is that column widths will not be handled by browsers.

  • CSS grid could be used in place of CSS tables after the subgrids feature is released.

Conclusion

This article describes a useful, CSS-Only, general solution for card style, responsive tables. While useful in many situations, the method is not a silver bullet - circumstances should be taken into account. Thanks for reading, I hope this article has been useful, feel free to share tips or to point out mistakes in the comments below!

Additional Reading

Citations

  • Contents
Edit Article
Share Article