Basics

Data tables in email – Part 2

This week I’m digging deep into the details of what we need to do with our data tables to ensure they work for assistive tech users – we’re starting with testing, then we’ll talk layout/design differences, and I’m even going to provide you with a short example code you can use to build out your first data table. Yay!

Checking to make sure you’re doing it right:

When I was going through the Trusted Tester course and exam I was introduced to a very clever accessibility tool called ANDI. I’m going to suggest you use it here but I cannot emphasize enough – this is a somewhat complicated tool and without the broader Trusted Tester training it’s easy to get lost/confused or misunderstand the results of this test. BUT – for the purposes of visually seeing the connectedness of data tables it’s very helpful.

This is probably a good time to go ahead and state that any accessibility checker can only get you so far – they can only check for programmatically available items and that’s… not a lot of what we need.

ANDI is a browser extension and it works on locally hosted webpages. (Yay! not many accessibility tools do this and hosting emails just to test them is kind of a pain)

So you’ll open your html email file in a browser (or you can use the view in browser link of an email if you don’t have the html) and open the ANDI tool.

ANDI lets you navigate either by entire tables or by individual cells — similar to how screen reader users move through tabular content.

Go to your data table and click inside at one of your table cells full of data. You’ll see that in addition to the data in that cell, you will also see the correlating headers that give you context on that data. Click the small arrow to go to the next table cell and you can see how the table headers have changed to support that content. Check out the “ANDI Output:” line at the bottom which shows what the screen reader should read out.

Screenshot of ANDI in table mode showing a table cell and the table headers included in the ANDI Output.

If we’ve created our e-receipt right, we’ll have the same experience:

Another ANDI screenshot, this one showing a correctly labeled table cell and table header combination. Here, the Unit Price header and the price of the itme are both included in the ANDI Output.

But if we’re using role presentation or not using the table header tag, our users don’t get that additional context:

Another ANDI screenshot, this one has not table headers so the cell content is the only thing in the ANDI Output.

What is this date? How is it important? How does it relate to the other information on the page??

This ANDI tool is absolutely great for visualizing if you’ve coded your data tables correctly and it’s way easier to understand and use then testing with a screen reader – though I do recommend doing that too so you can better understand that experience – the ANDI tool is a good bridge because it can be kinda confusing to understand how that data works together.

Advanced Accessibility within tables

As part of my research for this post I scoured Really Good Emails for e-receipts and order confirmations. Hilariously as I was close to finishing this they gave us this list of 196 Best Receipts so feel free to check these out to see the variations. As you scroll these, pay attention to how often headers are missing, fragmented, or visually implied but not programmatically present. You’ll notice that…

Some of these are just … not… tabular data. Do we have to design it to be tabular data?

No – though I would argue that probably a lot of these could be tabular, but if you’re being handed a design that doesn’t have to be tabular data – it doesn’t have to be tabular data. WCAG doesn’t require content to be tabular just because it looks like a receipt.

Here’s a few examples:

This is an Uber One receipt with just a few lines of text describing the purchase instead of a table format. Screenshot of a Disney Plus e-receipt where the content is just line after line and not really in a tabular format.

This Typeform e-receipt is in a paragraph/list format rather than a table, very simplistic. This is a Bally Sports + e-receipt where the content of the receipt is in a paragraph format and not a table.

So these could definitely be redesigned to be a standard receipt, but from a purely accessibility perspective – there isn’t an overwhelming need to be tabular data. These aren’t complicated, there’s really not a lot of information. Compare that to a recent art supply run I did where every single pencil I purchased was its own table row – and that wouldn’t work in these more simplistic solutions.

How does the table header situation work on some of these more detailed receipts?

As I was building and testing my own data tables I encountered several variations of the theme where we have a full table with headings that are going in different directions/overlapping:

Screenshot of an Order Receipt with two placeholder items with table headers for Description and Amount, but the bottom half of the table includes new information like the Sub Total, Taxes and Total.

If you try to remake something like the above you’ll run into the same issues I had – which is the question of how to organize these data cells so they make sense to screen reader users.

Problem: The text in the boxes on the top are all table headers, but so is the text in bold along the left below the items. Screen readers don’t know which headers apply to which data, so they announce too much at once. Here’s what it looks like on ANDI:

Another ANDI screenshot showing the same data as above but when I select one of the product items, several table headers are also highlighted resulting in a very long and confusing ANDI output.

Instead of hearing one column header at a time, users hear multiple unrelated headers announced together. That’s bad!

Solution: Use header ids. When you dig deep into the w3 table tutorial you run into this solution here: https://www.w3.org/WAI/tutorials/tables/multi-level/#table-with-multiple-column-headers-in-each-column

You only need header IDs when simple row/column headers aren’t enough – usually when headers change direction or meaning within the same table. (Like our example)

In this scenario, you have to connect these tables yourself so that they are connecting correctly.

Another ANDI screenshot showing how when we use IDs we can limit what data within the table are related to each other.

When you use the header ids it helps these items stay connected together – consider it a kind of map that describes how those pieces are and are not connected together. (If you’re an 80’s kid and you played battleship this will feel familiar)

Here’s the code for this guy you can use as a base to see how it’s supposed to work:

As you can see, this would be pretty straightforward/easy to pull into loops for those products. Nothing fancy, just making sure we’re connecting all those pieces with the headers and ids. This text can be just about anything – so you can make them relevant to yourself if you need to like “subtotal” instead of “co1” as long as it’s text/numerals, I would stay away from special characters.

You can learn more about Data tables at the W3 Tables Tutorial which has lots of other good examples to get you going.

What about where most of the content isn’t tabular, but some of it is?

I couldn’t bother myself to create a generic example of this so using one from my recent flight to Stitch headquarters, but this flight confirmation has a lot of info:

Screenshot from my inbox of a flight receipt showing my flight information and the costs involved in a table in the lower left.

This could definitely be better designed – from an accessibility perspective it’s a bit chaotic, but using it as an example for our data tables – really most of this info could just be headings and paragraphs. Or probably a bit more precise – some of these could be more like lists but that would be pretty hard to control in email so I would do it as headings/paragraphs.

However – that bottom left chart is tabular data (surprise!). Usually we would have that Total Cost as the caption for the table, but because the rest of this content is navigable via headings, we should continue that here and have the Total cost be a heading and the data underneath be the standard table. This lets screen reader users navigate the content by headings first, then switch into table navigation only when they hit the tabular data.

It is totally fine to have other non-tabular data around the data table – just think through how folks would navigate through it.

Conclusion

When it comes to tables in email, context is everything. Tables are an essential part of email development – our old reliable tool for building layouts that work across email clients – but not every table is created equal. Using role=”presentation” (or role=”none”) helps assistive technologies understand when a table is purely visual. But when a table actually conveys data, we have to build it as a data table, not a layout table.

Data tables, like those used in e-receipts or order summaries, rely on semantic structure to communicate relationships between table headers and data cells. Those relationships are what make the information meaningful and navigable for assistive tech users. If we treat a data table like a layout table, we strip away that context – leaving users with disconnected bits of information that sighted users never have to think about.

So the takeaway is this: Use role=”presentation” for layout tables – and only for layout tables. When your table represents structured data, keep the semantic tags (<table>, <th>, <td>) intact, plan your content relationships intentionally, and test with tools like ANDI (and ideally a screen reader) to confirm that everything is actually connected the way you think it is.

And here’s where I have to admit something: I truly thought this was going to be a tidy two-part series. But email, as it so often does, had other plans.

Because even when you do everything “right”: semantically correct tables, thoughtful relationships, clean structure – email clients gonna do what email clients gonna do. More specifically: Gmail’s gonna gmail and at least some of the “supposed to” and “should” in today’s post are gonna fall apart. 

So… there’s going to be a Part 3. We’ll talk about how email clients break otherwise well-built data tables, what compromises actually make sense, and how to choose the least bad option when perfectly accessible isn’t actually possible.

Le sigh.

Explore post tags