Bulletproof responsive datatables in HTML emails


Most clients will at some point want to include a massive data table within your email.

Problem is, while it looks great on a desktop, with usually around 80% of emails read on a small mobile device, this is what they’ll be seeing:

Responsive datatable view on a mobile phone








Or perhaps the email client will helpfully ‘fit’ it to the screen — or the coder will suggest to make it an image:

No, you are not supposed to be able to read this

And with some tricks we can get this, with horizontal scrolling — on some email clients:

Ah hah! Finally readable — for part of the table

Many plucked hairs and coffees later, we start to question the whole endeavour.

Does all this data really need to go into an email?

Could you just put the data on your website?

Problem is, in enterprise systems, the data within the table is going to be mostly variable (and private), so it’s not something you can easily supply on your public website. Even if you had the security and privacy worked out, you’re forcing your customers to take an extra step to view it on a website.

Also, in an enterprise world, complexity is increased if the web environment is looked after by a different department to the email communications. You don’t want to involve another department if you don’t have to — and the client doesn’t want to pay any more than they must.

Solution 1: Responsive Data Table

Data tables are notoriously tricky to make responsive at the best of times. There are plenty of advanced JavaScript solutions for doing this on the web. None of which work, of course, in email, as they strip out all JavaScript. Advanced CSS suffers the same fate, mostly.

While some might be satisfied with the vast majority of email clients supporting @media queries to give us different layouts for mobiles, those using Gmail with IMAP and POP accounts will have a poor experience. If some people are stuck on older devices with old apps of Gmail Android, Yahoo! Mail, Windows Phone 7 or 8, they too won’t have @media query support.

So in order to be as inclusive as possible, we’ll need to stick with the hybrid email approach.

I’m going to show you a solution that covers 3 column tables. The same technique can be used for more columns.

So the standard 3 column hybrid template is this for desktops:

[col1] [col2] [col3]

And the following for mobiles — without @media queries, because the hybrid design automatically moves the blocks down when they’re too wide to fit their container:




Problem is, this code separates the columns into different tables. It’s not possible to have the hybrid email design together with a seamless responsive datatable. Or is it?

Turns out, responsive datatables are possible with the hybrid email design, in a sleight-of-hand type way.

Here’s the main trick: You put the table headers into each table in each column, and hide the extra headers on desktop. Then, you line it up so it looks as though they’re one table!

Step 1: Add the data to separate tables in each of the three columns, repeating the headers

This step will setup the scaffolding, and looks a bit messy:

Step 1: Three columns of data in the hybrid layout (with added outline)

You can easily do column headings too (just add a <th> row to the top of each table).

But the mobile view looks fine already (for the web, it might be normal to develop mobile-first, but for emails, we generally have to develop desktop-first, because only mobiles have support for changing their interface (with @media queries. Here, we are in fact developing mobile-first).

Step 1: Mobile layout

The headers are the only repeated data. You don’t need to do a ghost table for Outlook, which might be forgotten and not get updated. The great part of repeating the headers is that it makes the tables quite accessible, without requiring excessive extra special code. The data is always next to a header, even though they are separated by a gaping (invisible) wall by virtue of the hybrid approach.

The code is fairly standard at this point (play around from this if desired).

Step 2: Hide the extra headers on desktop

We have two options.

Option 1: hide headers by default

We can hide the headers by default, and then show them only on mobiles through @media queries (so, going back to a desktop-first approach). But that wouldn’t support Gmail IMAP/POP and some other minor email clients because they don’t support @media queries.

The desktop-first approach leaves mobile devices that don’t have support for @media queries without headers, which is potentially disastrous:

Hiding headers by default can lead to poor user experiences

You might like to still use this approach as it’s easier, but, ethically and professionally we should be as inclusive as possible. For me, I choose to include as many as possible, even minority groups, because all people are made in the image of God (Genesis 1:27), and therefore worthy of respect and dignity.

So let’s see if we can be as safe as possible by showing headers by default, rather than encountering the possibility they might be hidden for some.

Option 2: show headers by default

Can we show the headers by default, and hide them on desktop? Impossible? Not at all!

We have a three pronged approach. For Apple devices, and Gmail, even desktops support media queries, so let’s go ahead and remove them easily:

@media screen and (min-width: 415px) { /* This is read "from a minimum of 415px and higher". Remember to set the width based on what makes sense for your data */
.datatable.hideondesktop th { /* Table headers */
display: none!important; /* Hide */
.data.column { /* Whole table */
max-width: 100%!important; /* Instead of 200px */

Some older ones (Outlook 2000, Comcast, T-Online.de) don’t respect display:none, so, we can edit the above and set all sizes to 0 too:

@media screen and (min-width: 415px) {
.datatable.hideondesktop th {
display: none!important;
color:#ffffff!important; /* set to background colour */
line-height: 1px!important;
min-width: 0!important;

For Outlook desktop, we must use their handy conditional code, and wrap a display:none; around the headers we don’t want to see — or, more easily, just happily insert mso-hide:all into the ones you already have. All other email clients will ignore this line, including Outlook iOS/Android.

<!--[if (gte mso 9)|(IE)]>
<div style="display:none;">
<th style="min-width: 100px;" scope="row">
<!--[if (gte mso 9)|(IE)]>

That’s a bit long, so, how about:

<th style="min-width: 100px; mso-hide:all;" scope="row">

For Outlook 365, they appear to support @media queries now, as long as you follow their rules. First I had to start a new <style> block (perhaps there’s some limit, or it stopped at a perceived error?) Second, I had .datatable.hideondesktop th as the css selector, which was translated to .x_datatable.hideondesktop th (notice the single “x_”). But in my HTML both the classes had the “x_” prefix, so the embedded CSS wasn’t matching. I changed my selector to .datatable.hideondesktop th, .datatable.x_hideondesktop th and it worked. .hideondesktop th works here too.

(*Old — reference only: For Outlook.com/365 users, they don’t normally respect embedded CSS (or display:none), we can use the [owa] selector and reduce the sizing to effectively 0:

<style type="text/css">
[owa] .outlookcomhide th { /* see above - as of early 2019 not necessary */
line-height: 1px!important;
min-width: 0!important;
max-width: 0!important;

(*Old — reference only: Yahoo used to not work, but now seems to support display:none and @media queries.)

Step 3: Consider Accessibility

*Ignore this if you are using column headers — it’s probably accessible already. The complete code with column headers (only slight changes — note th.hideondesktop) is at this CodePen.

As it stands, there are separate tables for each extra column you add. This means there is data that is separate from the headings, which is pretty bad for accessibility (on desktops only — mobiles show each table consecutively).

It’s bad because:

  • If you use a screen reader (whether because you’re visually impaired or, say, driving), it won’t read across the rows. It reads them in code order, not sight order. It won’t read the headers, because they are set to display:none
  • If you have links or tab’able items in the table, you won’t tab through them in the order you expect.

I tried a number of approaches and the best seems to be a rather blunt jedi mind-trick, rather than anything brute-force (adding aria- labels and such).

What I mean is that if we make the fonts tiny and the same colour as the background, it’s practically the same as display:none but screen readers will read it.

So in the embedded CSS, just remove display:none:

@media screen and (min-width: 415px) {
.hideondesktop th {
color:#ffffff!important; /* set to background colour */
line-height: 1px!important;
min-width: 0!important;

But what this means for the Outlook (desktop) clients is we’ll have to change the mso-hide:all directive to the rather ugly conditional code:

<th style="min-width: 100px;" scope="row"> /* remove mso-hide:all; here */
<!--[if (gte mso 9)|(IE)]>
<font style="font-size:1px; color:#ffffff; line-height: 1px ">

<!--[if (gte mso 9)|(IE)]>


Don’t forget to change the color to your background color.

If you have to remove display:none for accessibility reasons, there may be some older email clients that will render the headers multiple times on desktop (e.g. Comcast). That may or may not be an issue for your particular audience.

Other potential options

Depending on your data, it could be a better idea to structure your datatable in columns rather than rows.

You could opt for making desktop tables look the same as mobiles (to have all headers show at all times).

Alternatively, the addition of column headers together with the row headers may be enough (but it depends how intuitive the data is).

The complete code

Putting it all together, you can see the completed code on CodePen below or Litmus to see the results in different environments and email clients (or this with the addition of column headers with only slight changes):

What if the data splits across two lines?

If some of the data is longer and wants to split to two lines, the tables may mis-align.

Table misalignment for varying lengths of data

Essentially, there is no way to dynamically ensure the heights match, because they are separate tables (and classes don’t work across all email clients). This will have to be tested with the real data so we can know what the needs are and manually set the heights.

<th style="min-width: 100px; height:30px;" scope="row">Header1</th>

Fortunately, the data is usually predictable so this is rarely an issue.

Solution 2: The responsive ‘card’ (another time, another post)

But wait! There’s an ever better solution — check back to view my next post on Responsive Card UI Design within HTML Emails.




Front-End Dev; UX Designer; INTJ “Architect”

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Stacks & Queues

How one Stormie Grew his Career at Livestorm Remotely

Wrap-around Indexing in Deques

Hire Node.js,PHP, Java developers

Spark for BI & Analytics: Databricks vs Azure Synapse Analytics

Synchronous execution of tasks with WSO2 EI Scheduled Tasks

GO (Google’s Programming Language) Part 2

Miniblog: Understanding open() in Python

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Nathan Keen

Nathan Keen

Front-End Dev; UX Designer; INTJ “Architect”

More from Medium

How to Add a System File Scan Desktop Shortcut on Windows 11/10

Temporary Insanity: Surviving the revenge of my enemies- entry#1

CS373 Spring 2022: Jay Park — Week 9

Formidable Italian Heavyweight Trio MEDUZA Announce ODIZZEA LIVE In Concert