Skip to Main Content
Zebras

Responsive Tables with Craft CMS, Table Maker, and Tailwind

I'm working on a site that the client needs to be able to create any number of tables with any number of columns and rows. Hand coding tables is not easy and one cannot expect a client to do that. Fortunately in Craft there is a plugin that makes it easy for clients to do just this.

Table Maker: A field type that enables your users to create their own columns, controlling the width and alignment of each:

I have the table maker field type in a matrix block allowing the client to add as many tables as they need to the entry.

A table with a lot of tables is not going to work on tablets or phones so we need to ensure that it looks good on both.

Table Maker Publish View

Above is how table maker works in the entry publish screen. Create as many columns as you want and the table content  section updates the columns automatically. Once you do that, fill in your table data and you're done.

Table Maker will output a table for you with one line of code but it's just a plain table and won't look like your designs. It's better to use the custom code option so you can have it match your sites design.

Responsive Table

In the above image we have two tables created with table maker and each has a different number of rows and columns.

The full code is below but first a couple of notes. The opening table tag we set the table to be flex flex-row flex-no-wrap at mobile and then at the md breakpoint we change it to a table with md:table table-fixed. Now the table acts like a table at larger than 768px and is flex layout below that.

Let's look at the table head code:

    
      <thead>
    {% for row in block.table.rows %}
    <tr class="flex flex-col mb-4 bg-purpleBrand flex-no wrap md:table-row md:mb-0 {{ loop.first ? '':'md:hidden' }}">
    {% for col in block.table.columns %}
      <th align="{{ col.align }}" width="{{ col.width }}" class="h-10 px-4 py-2 font-normal text-white">{{ col.heading }}</th>
    {% endfor %}
    </tr>
    {% endfor %}
  </thead>
    
  

To get the mobile thead to look correct we need to output it for each row of the table. At desktop view we need to hide the additional rows in the head. First we loop through all the table rows and then for each table row we loop through the columns.

The extra rows are hidden on desktop with this conditional in the first loop {{ loop.first ? '':'md:hidden' }} which adds a md:hidden class if it's not the first loop.

Zebra Striping

Zebra Striping is set at desktop using this {{ cycle(['md:bg-white', 'md:bg-greyBrand-light'], loop.index0)}} on the table row of the tbody tag. the twig cycle tag makes it easy to alternate classes. notice these classes are prefixed with md:  that's so they are only applied at desktop.

Mobile zebra striping needs to be done seperately as each cell of the row is on it's own line. This is done on the td tag{{ cycle(['bg-white md:bg-transparent', 'bg-greyBrand-light md:bg-transparent'], loop.index0)}} notice that the striping is set, but at md: we set the background to transparent so we can see the striping set above at desktop.

No borders at desktop. However at mobile the table looked strange without adding a top, bottom and right border. on the td again. The last cell gets a border bottom and the first cell gets a border top and all cells get a border right.

    
      {{ loop.last ? 'border-b md:border-b-0': ''}} {{ loop.index =='1' ? 'font-bold border-t md:border-t-0':'' }} border-r
    
  

Complete code block

And the complete code block is below.

    
      <table class="flex flex-row flex-no-wrap w-full mb-8 table-fixed md:table">
  <thead>
    {% for row in block.table.rows %}
    <tr class="flex flex-col mb-4 bg-purpleBrand flex-no wrap md:table-row md:mb-0 {{ loop.first ? '':'md:hidden' }}">
    {% for col in block.table.columns %}
      <th align="{{ col.align }}" width="{{ col.width }}" class="h-10 px-4 py-2 font-normal text-white">{{ col.heading }}</th>
    {% endfor %}
    </tr>
    {% endfor %}
  </thead>

  <tbody class="flex-1 md:flex-none">
    {% for row in block.table.rows %}
    <tr class="flex flex-col flex-no wrap md:table-row mb-4 md:mb-0 {{ cycle(['md:bg-white', 'md:bg-greyBrand-light'], loop.index0)}}">
    {% for cell in row %}
      <td align="{{ block.table.columns[loop.index0].align }}" class="h-10 px-4 py-2 border-r md:border-r-0 {{ loop.last ? 'border-b md:border-b-0': ''}} {{ loop.index =='1' ? 'font-bold border-t md:border-t-0':'' }} {{ cycle(['bg-white md:bg-transparent', 'bg-greyBrand-light md:bg-transparent'], loop.index0)}}">{{ cell }}</td>
    {% endfor %}
    </tr>
  {% endfor %}
  </tbody>
</table>