Skip to content

Mobile Friendly Tables

Building a table that works well on smaller screens is tricky, but I've come up with a simple CSS-only approach that works well for the 80% use case.

An Example #

Check out the example below by resizing your browser window:

My Most Played Songs of 2018
Title Artist Album Released
Our Song Radiator Hospital Something Wild 2013
DEB Thin Lips Riff Hard 2016
Crown Victoria Lithuania White Reindeer 2017
Phoenix Slaughter Beach, Dog Birdie 2018
Enter Entirely Cloud Nothings Life Without Sound 2017
the cool Oso Oso The Yunahon Mixtape 2017
How Simple Hop Along Bark Your Head Off Dog 2018
Shark Smile - Edit Big Thief Shark Smile (Edit) 2017
Raining in Kyoto The Wonder Years Sister Cities 2018
Dead-Bird McCafferty Beachboy 2014
I Never Wanted You Headphones Headphones 2005
Your Best American Girl Mitski Puberty 2 2016

How It's Done #

The first step to creating a good table is to ask yourself "Do I actually need to use a table?". Many times there's better ways to present tabular data than just a boring table, Steve Schoger has some great tips on organizing tabular data.

If it turns out you really do need a table, check out the code in the next section.

The Code #

First, let's take a look at the markup:

<table class="table-collapse">
<th class="text-right">Release Date</th>
<td data-heading="Title">Our Song</td>
<td data-heading="Artist">Radiator Hospital</td>
<td data-heading="Album">Something Wild</td>
<td data-heading="Release Date" class="text-right">2013</td>

There's two important thing to note in the markup above:

  1. The table-collapse class will apply the collapsible behavior to the table.
  2. The data-heading attribute on each <td> element should describe the table cell data.

Now, for the CSS:

@media (max-width: 768px) {
.table-collapse {
width: 100%;
.table-collapse > tfoot,
.table-collapse > thead
display: none;
.table-collapse > tbody,
.table-collapse > tbody > tr,
.table-collapse > tbody > tr > td,
.table-collapse > tbody > tr > th
display: block;
width: auto;
.table-collapse > tbody > tr {
padding-top: 0.5em;
padding-bottom: 0.5em;
border-top: 1px solid theme('colors.gray.200');
.table-collapse > tbody > tr > th {
border: none !important;
padding: 0.25em 0.5em;
text-align: left !important;
.table-collapse > tbody > tr > th:first-child {
padding-left: 0.5em;
.table-collapse > tbody > tr > th:last-child {
padding-right: 0.5em;
.table-collapse > tbody > tr > td {
border: none !important;
padding: 0.25em 0.5em 0.25em 35%;
box-shadow: none !important;
text-align: left !important;
position: relative;
.table-collapse > tbody > tr > td:first-child {
padding-left: 35%;
.table-collapse > tbody > tr > td:last-child {
padding-right: 0.5em;
.table-collapse > tbody > tr > td::before {
content: attr(data-heading);
position: absolute;
top: 0.25em;
left: 0.5em;
width: 35%;
padding-right: 0.25em;
white-space: nowrap;
z-index: 1;

View the code for this table as a gist.

Thanks to the media query in the first line above these styles are applied when the browser window is less than or equal to 768px wide. The table's header and footer are hidden and the cells in each row stack vertically using display: block. Each of the table cells also have padding-left: 35% which allows a healthy amount of space for the ::before pseudo element to sit to the left of the cell's data using position: absolute. The content: attr(data-heading) line dynamically pulls text from the data-heading attribute in the HTML and uses that text as a new label for the cell. The best part is the CSS attr() function has support all the way back to IE 8 when used as a content value.


No webmentions posted.