Mobile Responsive Table with TailwindCSS

mmcclure11

Meks (they/them)

Posted on January 16, 2023

Mobile Responsive Table with TailwindCSS

Recently I pair programmed with a Senior Software Engineer and part of the story we were working on was implementing the design for a list of users with their relevant data displayed. The design looked similar to this:

Image description

We also knew we wanted it to be mobile responsive even though we didn’t have any designs for that.

Accessibility Concerns

We first tried building it using a description list dl with dt which defines the terms/names and dd which describes each term/name. We actually got really close to the design using this, but then we used a screen reader to see what the experience would be like. It was at best cumbersome, and at worst unusable. The problem is that this is actually a list of tabular data; even if it doesn’t look like it based on the design and the lack of traditional headers. The way the screen reader goes through each definition is by reading the term then the description. This means that the information is split up so you can’t scroll through the list of emails, instead you have to go through all the elements of the description list to get to a specific email. When using table tags, the screen reader can read down each column of data which is a much better experience.

Building the Table

So we scrapped the description list and built it using the HTML table element and used sr-only to hide the headers from a visual user but including that important information for screen readers.

Once we had the table data we started styling each cell in td.



  <table>
    <thead class="sr-only">
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th>Company</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="px-4 py-3 text-gray-900 bg-gray-200 first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:rounded-br-lg">Frodo Baggins</td>
        <td class="px-4 py-3 text-gray-900 bg-gray-200 first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:rounded-br-lg">fbaggins@mail.com</td>
        <td class="px-4 py-3 text-gray-900 bg-gray-200 first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:rounded-br-lg">Fellowship of the Ring</td>
        <td class="px-4 py-3 text-gray-900 bg-gray-200 first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:rounded-br-lg">Active</td>
      </tr>
    </tbody>
  </table>


Enter fullscreen mode Exit fullscreen mode

The idea is that we want to reuse the classes for each cell, so we use the first and last modifiers to target rounding the border just for the top and bottom left for the first column, and the top and bottom right for the last column.

Image description

To add the tag-like styling to “Active“ we wrap it in a span:



<span class="float-right rounded-md bg-green-600/50 px-4 py-px text-xs font-semibold uppercase text-green-900 antialiased">Active</span>


Enter fullscreen mode Exit fullscreen mode

Image description

At this point we wanted to add more rows, but didn’t want to continue repeating reused classes. We were working in a Phoenix LiveView app so we created components. In the interest of focusing on the Tailwind here, I will pull the reused classes into the CSS file and use @apply.

CSS:



@tailwind base;
@tailwind components;
@tailwind utilities;

.td-class {
  @apply px-4 py-3 text-gray-900 bg-gray-200 first:rounded-tl-lg first:rounded-bl-lg last:rounded-tr-lg last:rounded-br-lg
} 


Enter fullscreen mode Exit fullscreen mode

HTML:



<table class="">
  <thead class="sr-only">
    <tr>
      <th>Name</th>
      <th>Email</th>
      <th>Company</th>
      <th>Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td class="td-class">Frodo Baggins</td>
      <td class="td-class">fbaggins@mail.com</td>
      <td class="td-class">Fellowship of the Ring</td>
      <td class="td-class">
        <span class="float-right rounded-md bg-green-600/50 px-4 py-px text-xs font-semibold uppercase text-green-900 antialiased">Active</span>
      </td>
    </tr>
  </tbody>
</table>


Enter fullscreen mode Exit fullscreen mode

Now we can add more rows easily and start styling the table. We need to add some spacing between each of the rows which we can do using Tailwind’s Border spacing for tables. Let’s also add the default text size of small and wrap the table in a div so we can center it using flex.



<div class="flex items-center justify-center">
  <table class="text-sm border-separate border-spacing-y-2">
    <thead class="sr-only">
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th>Company</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="td-class">Frodo Baggins</td>
        <td class="td-class">fbaggins@mail.com</td>
        <td class="td-class">Fellowship of the Ring</td>
        <td class="td-class">
          <span class="float-right rounded-md bg-green-600/50 px-4 py-px text-xs font-semibold uppercase text-green-900 antialiased">Active</span>
        </td>
      </tr>

      <tr>
        <td class="td-class">Bilbo Baggins</td>
        <td class="td-class">bbaggins@mail.com</td>
        <td class="td-class">Thorin’s Fellowship</td>
        <td class="td-class">
          <span class="float-right rounded-md bg-green-600/50 px-4 py-px text-xs font-semibold uppercase text-green-900 antialiased">Active</span>
        </td>
      </tr>
    </tbody>
  </table>
</div>


Enter fullscreen mode Exit fullscreen mode

Image description

We still need to add the tags for when a user’s status is “Pending” or “Suspended“, so let’s add a few examples for that.



<div class="flex items-center justify-center">
  <table class="border-separate border-spacing-y-2 text-sm">
    <thead class="sr-only">
      <tr>
        <th>Name</th>
        <th>Email</th>
        <th>Company</th>
        <th>Status</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td class="td-class">Frodo Baggins</td>
        <td class="td-class">fbaggins@mail.com</td>
        <td class="td-class">Fellowship of the Ring</td>
        <td class="td-class">
          <span class="float-right rounded-md bg-green-600/50 px-4 py-px text-xs font-semibold uppercase text-green-900 antialiased">Active</span>
        </td>
      </tr>

      <tr>
        <td class="td-class">Peregrin Took</td>
        <td class="td-class">pippin@mail.com</td>
        <td class="td-class">Fellowship of the Ring</td>
        <td class="td-class">
          <span class="float-right rounded-md bg-green-600/50 px-4 py-px text-xs font-semibold uppercase text-green-900 antialiased">Active</span>
        </td>
      </tr>

      <tr>
        <td class="td-class">Bilbo Baggins</td>
        <td class="td-class">bbaggins@mail.com</td>
        <td class="td-class">Thorin’s Company</td>
        <td class="td-class">
          <span class="float-right rounded-md bg-yellow-600/50 px-4 py-px text-xs font-semibold uppercase text-yellow-900 antialiased">Pending</span>
        </td>
      </tr>
      <tr>
        <td class="td-class suspended-text">Boromir of Gondor</td>
        <td class="td-class suspended-text">boromir@mail.com</td>
        <td class="td-class suspended-text">Fellowship of the Ring</td>
        <td class="td-class">
          <span class="float-right rounded-md bg-red-600/50 px-4 py-px text-xs font-semibold uppercase text-red-100 antialiased">Suspended</span>
        </td>
      </tr>
    </tbody>
  </table>
</div>


Enter fullscreen mode Exit fullscreen mode


.suspended-text {
  @apply text-gray-500
}


Enter fullscreen mode Exit fullscreen mode

Image description

For the row with a status of “Suspended“ the text color is a lighter shade of gray. Since the current color comes from the CSS file as part of the td-class, we have to add another class so that when the CSS is compiled it will overwrite it with the suspended text color. CSS that is in the CSS file is compiled after the inline utility classes. The consequence of this is that if you try td-class text-gray-500, it will still render as text-gray-900. This is one reason why it’s highly recommended that you componetize your reused classes instead of using @apply.

Great! This looks very close to the design we were given to work with! But, if you resize it down to a mobile phone, it doesn’t look great and in fact some of the text is cut off.

Image description

Let's fix that! We’ll make it very simple and render each row in a more readable way.

Making the Table Mobile Responsive

To prevent the text from being cut off at the front, we add a breakpoint of sm:justify-center and some margin to the containing div.



<div class="flex items-center sm:justify-center ml-4 sm:ml-0">


Enter fullscreen mode Exit fullscreen mode

We add class="tr-class" to each table row and make it a flex container with a column direction so that information inside each card stack on top of each other with some margin in between in each card.



.tr-class {
  @apply flex flex-col mb-4
}


Enter fullscreen mode Exit fullscreen mode

Image description

It looks a little goofy because of the border rounding in our td-class that we implemented for the first iteration, we need to adjust the rounding for the mobile size.



.td-class {
  @apply px-4 py-3 bg-gray-200 first:rounded-t-lg last:rounded-b-lg
} 


Enter fullscreen mode Exit fullscreen mode

Image description

This looks much better! But if you resize it to tablet or desktop size, it stays as those cards instead of expanding to long rows. We need to add a breakpoint of sm:table-row to the tr-class. This adds the display table-row so it will behave like it did before the flex attributes were added.



.tr-class {
  @apply flex flex-col mb-4 sm:table-row
}


Enter fullscreen mode Exit fullscreen mode

Now it expands out to the rows as before! For the last touch, we need to re-implement the rounding of the table edges, this time with breakpoints.

As an aside, this is why, when you are able, it is highly recommended that you build for your smallest screens first and then work your way up. It’s a bit trickier going the other way around, but we opted for that approach since we only had one set of designs.

We first remove the border radius from the top and bottom when it hits the sm breakpoint with sm:last:rounded-b-none sm:first:rounded-tl-lg. We then add our original radii.



.td-class {
  @apply px-4 py-3 bg-gray-200 first:rounded-t-lg last:rounded-b-lg sm:first:rounded-t-none sm:last:rounded-b-none sm:first:rounded-tl-lg sm:first:rounded-bl-lg sm:last:rounded-tr-lg sm:last:rounded-br-lg
}


Enter fullscreen mode Exit fullscreen mode

Image description

And that’s it! A screen reader friendly and responsive list of data. To play around with the styling and see the final state of the code, you can check out this Tailwind Play.

Resources

Tailwind Components
Tailwind Docs

💖 💪 🙅 🚩
mmcclure11
Meks (they/them)

Posted on January 16, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related