Let’s bring <table> to the table, again.

Mark Caron
11 min readNov 14, 2016
The Brady Bunch as a <table> is not the proper use of a table.

Hopefully, by now, we should all agree that you should never use tables for layout in HTML, and that tables are not evil when used appropriately. In other words, we should agree that tables are strictly intended for tabular data.

However, there are instances where tables of tabular data can be bad. The distinction is subtle, but it’s worth paying attention to — it’s a difference of context. And, this context is what I’d like to bring to the table (so to speak).

First, what constitutes tabular data?

This semantic question is what I find the most interesting, because I find that it’s the question that — most often — doesn’t get asked properly if even asked at all.

Strictly speaking, tabular data is information that requires a row and column structure to decipher. This definition compels you to ask yourself the following questions every time you feel like using a table for markup:

  • Does the information share a common attribute across a row or column?
  • Can the order of the information be rearranged and still make sense?

Maybe? No?

If your answer to any of these is not an absolute “yes,” then you should utilize a different, more semantic construct.


If you answered “yes” to both questions, then you have determined that your information is, in fact, tabular data.

But, you should still consider the following:

  • Can this information be expressed in another way and still maintain its meaning?

This is the context part. If it can be communicated in another way, like a simple list or definition list without losing its semantic value, then it certainly doesn’t need to be a table. And, if it doesn’t need to be a table, then it probably shouldn’t be a table.

“Simplicity is the ultimate sophistication.”
— Clare Boothe Luce

Always default to the simplest form of semantic expression possible. This is true for almost every facet of life, especially communication and information architecture.

Tables are not simple.

They’re not simple to code properly, nor are they simple for unsighted users (more on these points later). Even “simple” tables are very complex structures. Yet, we often default to HTML tables to present simple relational information that could be more easily or more semantically expressed in other forms.


Well, I believe it is because we’re creatures of habit. We see others do this. Or we’ve done it before. Or we’re just used to it. But popular opinion, old habits, or group norms don’t always mean something is right. And, I’d argue that we have plenty of examples throughout human history that support this claim.

If this is beginning to sound a bit too philosophical, then good! HTML is about applying meaning to information. This is semantics, and semantics is philosophical.

Therefore, ask yourself: “Could this information be represented in a simpler form?”

Simple vs. complex relationships

Simple one-to-one or one-to-two -type relationships should never be tabular. Could it be tabular? Sure. There is just no need for the added complexity that comes with marking up a table. Wikipedia has these very same guidelines.

Figure 2: Example of a one-to-one table showing the number of apples (5), oranges (3), and lemons (6). Note: This shouldn’t be a table.

In the preceding figure, column one shares a common attribute: name. And, column two shares the attribute “count” or perhaps it’d have the heading “total.” But, aren’t these shared attributes obvious? And, could this information be understood with out them? Like, perhaps, in a list:

  • 5 apples
  • 3 oranges
  • 6 lemons

Not only is this simpler to type or code, but it’s far simpler to read. It’s also scans as easily as the table version. It’s important to note, too, that in some cases, restructuring information into simpler forms might require some purposeful editing, like I did with the fruit totals. “5 apples” is the same as “Apples: 5,” but the former works better in a list because it reads more naturally.

However, in the case of more complex relationships — that would otherwise be too difficult to make sense of in other forms — we must use a table. These complex relationships include information like pricing and feature tables, demographical data, multi-faceted data, comparison data, and other structures like schedules.

Figure 3: Example of a comparison table using information from Beer Advocate. Header columns are Beer Name, Brewery, Style, and Beer Advocate rating.

We could represent this information as a list. We would be forced to provide labels for each data point, like “Style: Belgian Quadrupel,” though, and that would be overly convoluted and nearly impossible to scan. Therefore, the added tax of properly constructing an accessible table is necessary for the benefit of the reader/user.

The inherent complexity of tables

As I mentioned throughout this post, tables are rather complex, and they are for the following reasons:

  • They semantically imply a rigid structure of rows and columns.
  • Due to the additional structural requirements, properly coding <tables> involves a lot of HTML tags.
  • And not just HTML tags, but the often overlooked and very necessary attributes that go along with them, like scope, id, and headers.

The problem, though, is that many developers, content writers and plugins that generate tables do so without regard to the last two items above — this is problematic. Typically, you’ll find this type of table structure on the web:

Figure 3: A very simple table showing Bob’s age (57) and favorite color (Blue). Note: This shouldn’t be a table either.
HTML for Figure 3.

But, this table is not properly marked up since it’s missing table headers. Instead, this would be slightly better:

Slightly better HTML for Figure 3, with table headers.

However, the problem with both of these tables is that this markup hasn’t gone far enough. For a simple table like this you might get away doing the minimum amount of work, but I don’t recommend this.

Enter: Screen readers

A person using a screen reader would hear the following for the table in Figure 3:

Table with 3 columns and 2 rows. Name, Age, Favorite Color, Bob, 57, Blue.

But, in a more complex table — like a class schedule — these would be much more cumbersome. One would hear:

Table with 10 columns and 7 rows. Department Code, Class Number, Section, Max Enrollment, Current Enrollment, Room Number, Days, Start Time, End Time, Instructor, BIO, 100, 1, 15, 13, 5, Mon,Wed,Fri, 10:00, 11:00, Magde, 100, 2, 15, 7, 5, Tue,Thu, 11:00, 12:30, Indge, 205, 1, 15, 9, 6, Tue,Thu, 09:00, 10:30, Magde, 315, 1, 12, 3, 6, Mon,Wed,Fri, 13:00, 14:00, Indge, BUS, 150, 1, 15, 15, 13, Mon,Wed,Fri, 09:00, 10:00, Roberts, 210, 1, 10, 9, 13, Mon,Wed,Fri, 08:00, 09:00, Rasid.

Could you tell me in what room Biology 205 is held?

“No sweat, Boba Fett.” From the sitcom Community. http://gph.is/12yaSKe

It’s not easy.

The only way to truly find this information as a non-sighted user is to enter some form of “table mode” in a screen reader and have it announce the table header for each cell as you move through the table. And, this is just one experience in one screen reader (ChromeVox). They all differ to varying degrees.

Let’s look at another example, a somewhat simple table that exists in the real world (and honestly shouldn’t be a table):

Figure 4: Another example of a one-to-one table, to show faulty column “headers” when a screen reader announces the columns. Note: This shouldn’t be a table either.
HTML for the table in Figure 4.

In figure 4, a user would hear the following in a screen reader:

Table with 2 columns and 7 rows. Cores, 2, RAM 3.5 GB, NICs, 1, Disk, 4, Target input [slash] output operations per second, 500 operations per disk, Resource disk location (ephemeral storage), [slash]mnt[slash]resource, Available data disks, 1 32.

At first, the reading of this table makes a bit more sense. The screen reader appears to read each row properly, but it’s actually just reading line-by-line as it would anything else. In this example, though, we have a different problem. If you had your screen reader announce the table header for the <td> cells, like “1 – 32” at the end, it would tell you that information corresponds to the row header “Available data disks” and the column header “2”. But, does it? Does “1 – 32” relate to the first <td> cell of “2”? Nope.

This is because tables are intended to be read across both axes. And for this reason it can make a seemingly simple “table” like this rather confusing for unsighted users. However…

Kindergarten Cop gif: “It might be a tumor.” I still find this scene hilarious. http://gph.is/1T5QqIa

Cue Schwarzenegger accent:
“It’s not a table. It’s not a table at all.”

It’s a list.

Sighted users scan

Sighted users and developers often take their ability to quickly scan a complex table and make quick visual associations for granted. But, a person with low visibility or someone who can not see will not be able to make these same visual associations.

Thus, it is always best to go with the simplest table structure possible.

Doing it right

When you decide to use a <table>, you must do it right. And, the right way is quite complex. It takes a lot of time and thought. So, be conservative in your use of tables.

To properly markup a table, you must add headers attributes, ids, scopes, and a <caption> element. These provide programatic associations within the table so that unsighted users can “scan” too (or more maybe “jump around”).

House of Pain gif of “Jump Around.” http://gph.is/1Q2ihW3

For more information on properly marking up a table, please read WebAIM’s guide.

Responsive layout woes

Another inherent issue with tables is the necessity of horizontal space. Naturally, the more columns you add to a table, the more width is required. While table width can occasionally be an issue on a larger screens, it is most certainly problematic on small devices where space is a precious commodity.

Tables are just rather inconvenient on mobile.

This leaves you to consider really one of three options:

  1. Allow the table to overflow and scroll horizontally.
  2. Breakdown the table on small screens using some JavaScript or CSS wizardry.
  3. Don’t use a table at all.

Option 3 may not be an option, especially if your information really does require it to be presented as tabular data. So, you may be left with an awkward, side-scrolling table that makes scanning difficult — scanning is arguably the very benefit of a table in the first place. Or, you can drag along some hopefully minimal added page weight with JavaScript. I happen to like TableSaw JS.

If you choose a purely CSS method like Chris Coyier’s, it will require foreknowledge of all table headers as they’re embedded in the CSS.

Tables are just complex beasts.

Table alternatives

Alternatives should only be used when the context allows — it must be simple enough to not require rows and columns to makes sense of the information.

Basic lists

Earlier, I alluded to using a simple list for cases like how much fruit you have. For slightly more complex datasets you can nest lists with headers.

Give definition lists some love

Definition lists are often overlooked. But, many key:value pairings could or perhaps should be marked up as definition lists. You may have seen their <dl> tags out there in the wild.

Let’s use the example in Figure 2 of fruit. This type of one-to-one relationship could be better expressed using a definition list. It’s semantic as well as easier to code, modify and style. Plus, definition lists can actually be responsive without any need for JavaScript plugins.

It would look something like this in its normal state:

Figure 5: A simple definition list of fruit.

You can even make it look fancier:

Figure 6: A fancier styling of the definition list of fruit in Figure 5.
Definition List HTML for Figures 5 and 6.

Or maybe not definition lists…

The only major caveat to using definition lists is how poorly they’re handled in some of today’s screen readers — not differentiating between definition terms (<dt>) and definition descriptions (<dd>) when counting the items in the list. Thus, figures 5 and 6 above would be announced as a “list of 6 items” instead of a “definition list of 3 items.” This is confusing to unsighted users.

“Why don’t you go start a ruiners club.” From the sitcom Community. http://gph.is/17Cwp2f

I was a major champion for definition lists until I came across this accessibility issue. Some argue, though, that we should still write HTML as semantically as possible (even though some screen readers stumble over them) with the hope that screen readers will catch up. Maybe they haven’t properly implemented support for definition lists because nobody uses them?

So, it’s your call on that semantic vs. accessibility decision. I’m torn.

Other options I would not suggest

There may be other potential options with HTML5. One that a lot of people are jumping on board with are Custom Elements. I’ve remained hesitant to use these due to an array of potential issues with accessibility, progressive enhancement, SEO, and compatibility in general. Remember: Web Components and Custom Elements are still considered “experimental” by the W3C. I don’t think we should be relying on experimental technology to add semantic value to content at all. Instead, progressively enhanced behavior is the proper outlet for this technology.

You can also go the route of so-called “div tables,” where you over-utilize the <div> tag (a contagious condition commonly called divitis) and a few headings to markup what somewhat resembles a table. You can even harness the power of ARIA roles, like row and rowheader,” to provide meaning. The belief is that this make it easier to have the table structure breakdown (or stack) on smaller screens.

But why not just use a table? It’s the same amount of markup and confusion.

Not to mention, you’d have to write a lot of CSS to get the <div> tags to render like a table on larger screens. Why not just write the same about of CSS to have the table stack on mobile? After all, if you need rows and columns to make sense of the data, then it should just be a table. Plus, this type of divitis bastardization I can’t get behind.

Wrap up or TL;DR

Tables are not evil when used properly — for tabular data. But, they are structurally complex, labor-intensive to code the right way, awkward on small devices, and cumbersome to unsighted users. We should always keep these inherent drawbacks in mind when we’re considering a table to structure our information.

If our dataset is simple and doesn’t require rows and columns to understand, then we should default to a far simpler construct, like a list or definition list.

If our dataset is complex, then using a table is the best approach. It’s just incredibly important that we take the time to follow WebAIM’s guidelines.

The simplist structure is always the best structure when it comes to content.

Comments & feedback

I am interested to hear your thoughts on this philosophy as well as any accessibility issues you have come across with tables or their alternatives.



Mark Caron

UX / DesignOps Manager at Red Hat. Obsessed with soccer, web standards & accessibility, and tacos.