Hey, It’s Still OK to Use Tables

Baby Boomerangutuang, a guy in a gorilla costume, without the head, but with a belt of doll babies.
Baby Boomerangutuang, one of the Tick’s students. He was just shouting It’s OK to play with dolls!

Consider this post to be the sequel to my 2012 post It’s OK to Use Tables. Here I will go into bit more detail based on the state of accessible efforts I see today.

In that post I identified two scenarios I see frequently as a result of developers blindly following the don’t use tables mantra of near-modern web development:

  1. Tables displayed via images, usually with useless alternative text,
  2. Tables assembled from the flammable tinder of <div>s.

Here we are in 2017 and the slow oozing of ARIA into frameworks and libraries has emboldened the anything-but-a-table movement — because developers feel they can re-create any table semantics they excise.

Couple this with some confusion about how to make a standard table accessible, and we have a bit of a mess. Enough of a mess that nearly every accessibility audit I perform has a non-<table> table in it.

I want to qualify that I am talking about data tables, not layout tables. Data tables have two axes of information. If you only have one axis of information, use a list.

I am going to give you enough information, hopefully, to make accessible, responsive tables on your own that are not a burden on you nor your users.

What you will find below:

Accessible Per WCAG 2.0 AA

This part is really easy. Just make a valid HTML <table>. You don’t even need that many elements. Avoid spanning cells, make sure you use <th> for headers, and adding a <caption> is nice of you. I made a CodePen of a simple table, and embedded it below.

See the Pen Minimal Table by Adrian Roselli (@aardrian) on CodePen.

Tables can be a bit more complex than this, though.

Complexity #1: Row Headers

Not all headers are for columns. Some headers are for a row, and this can be throw off some developers. It just so happens that WCAG has some guidance for us in technique H63: Using the scope attribute to associate header cells and data cells in data tables.

All you need to do is:

Conveniently, the WCAG technique offers a sample table, which I recreated on CodePen and also embedded below.

See the Pen Table with Row Headers by Adrian Roselli (@aardrian) on CodePen.

Complexity #2: Spanning Cells

Generally you want to avoid spanning table cells. This can lead to unnecessarily complex and confusing tables. If you have to, there just happens to be a WCAG technique to support it, specifically H43: Using id and headers attributes to associate data cells with header cells in data tables.

The code can look complex, but the approach is straightforward.

Once again, the technique offers a sample table, which I recreated on CodePen and also embedded below.

See the Pen Table with Cell Spans by Adrian Roselli (@aardrian) on CodePen.


You may find it hard to believe, but for a straightforward data table, you probably do not need any ARIA. Screen readers in particular have been dealing with tables for far longer than the existence of ARIA.

Sometimes a table is just a grid of data and sometimes a table is a control (widget) that users modify (or modify its data, such as a spreadsheet). The control/widget use case produced some ARIA to account for it. This ARIA is what is so frequently abused when developers over-code to avoid using a <table>.


ARIA has a few roles that define and describe data grids. The definitions that follow come from ARIA 1.1 (these are truncated).

grid (role)
A composite widget containing a collection of one or more rows with one or more cells where some or all cells in the grid are focusable by using methods of two-dimensional navigation, such as directional arrow keys.
gridcell (role)
A cell in a grid or treegrid.
rowheader (role)
A cell containing header information for a row in a grid.
columnheader (role)
A cell containing header information for a column.
row (role)
A row of cells in a tabular container.
rowgroup (role)
A structure containing one or more row elements in a tabular container.

Please note that those roles are primarily intended to be used for interactive grids, such as a spreadsheet. In these controls each cell may be editable and can be navigated using a series of keyboard shortcuts.

ARIA Table

Since developers were applying grid roles to what should be regular tabular data, ARIA 1.1 added the following role in response:

table (role)

[ARIA 1.1] A section containing data arranged in rows and columns. See related grid.

The table role is intended for tabular containers which are not interactive. If the tabular container maintains a selection state, provides its own two-dimensional navigation, or allows the user to rearrange or otherwise manipulate its contents or the display thereof, authors SHOULD use grid or treegrid instead.

Authors SHOULD prefer the use of the host language’s semantics for table whenever possible, such as the HTML table element.

If it’s not a widget, don’t use role="grid", use role="table". Ideally you do this by just using <table>, because then you can skip the role="table" as <table> has that role implied.

ARIA Patterns

Putting all the ARIA roles into practice and understanding which to use can be tricky. The WAI-ARIA Authoring Practices 1.1 document is intended to be a guide for developers, providing copy-paste-ready code for common user interface patterns.

Grid Pattern

The ARIA Authoring Practices document defines a pattern for grids, providing guidance and sample code. This sample code includes the nesting requirements and the ever-complex necessary keyboard navigation support covering the following: , , , , Home, End, Page Down, Page Up, Ctrl + Home and Ctrl + End.

There are even more keys to support if you allow selection of cells, rows, or columns.

In short, if you start using role="grid" on tabular data then you are telling users that it is a fully interactive widget. You also need to implement all the keyboard support above and more.

Table Pattern

There is a placeholder in the ARIA Authoring Practices document for tables, though it points back to the grid pattern and the following decision flow for choosing between a table and a grid:

To restate: a grid is a single tab stop on the page (which then manages focus within), while a table is not.


With a little creativity, you don’t need to worry about narrow viewports. Granted, not all layouts will work in all cases, but there is no reason to dump <table>s in service of responsive layouts. Here are a couple methods you can use.

To reiterate, I am just talking about width responsiveness. I have another post coming that goes into more detail.

Just Let It Scroll

Yep, you can just let a table scroll off the screen (within a bounded container). It’s not ideal and can be offensive to some, but it is at least simple and straightforward. The catch is that you need to make the scrolling area keyboard accessible, but we already know a solution for that.

I made a sample table in a scrolling container. I added tabindex="0" so keyboard users can get to the scrolling area and use the arrow keys to scroll back and forth. I added role="region" and aria-labelledby so that screen reader users who encounter this tab stop will understand what it is. I have embedded it below or you can go directly to it at CodePen.

See the Pen Responsive (Scrolling) Table by Adrian Roselli (@aardrian) on CodePen.

I show how this keyboard-friendly technique can be used in individual cells as well in an old post.

Use CSS Layout

For some tables you can just use CSS to re-arrange the entire thing. Just because table elements come with built-in table layout styles doesn’t mean you are restricted from changing them.

Between CSS flex and CSS grid, we have more options than ever to make responsive tables that can adapt to nearly any screen regardless of your data.

Note that as soon as you start messing with the display properties of a <table> (using the display property), it no longer registers as a table to a screen reader. I will go into more detail on that in my next post.

I have made an example that uses neither CSS flex nor CSS grid, for backward compatibility reasons. Instead it relies on the lowly CSS display: block. Obviously in my example it won’t work for anything more complex than simple values, but it at least shows you what is possible. You may view it on CodePen or just play with the embed below.

See the Pen Responsive Table by Adrian Roselli (@aardrian) on CodePen.

Use Them Both

I have more detail and a deeper dive into this example in the post A Responsive Accessible Table. There I take this apart and explain all the code going into it so you can give it a go on your own.

See the Pen Responsive Table That Also Scrolls if Necessary by Adrian Roselli (@aardrian) on CodePen.


  1. Use <table>s for tabular data.
  2. Don’t use ARIA grid roles unless you are making a spreadsheet (or something like it).

That’s it.

Update: February 20, 2018

I expanded on how tables are affected in my post Tables, CSS Display Properties, and ARIA.

Update: July 9, 2018

Steve distills it nicely in a tweet:

Update: December 30, 2018

Steve Faulkner shows us the accessibility tree and demonstrates how tables are parsed by screen readers in his 24 Accessibility post Tables and Beers. In a follow-up post, Tables, Tequila and Beer, he also confirms my assertions that scope="col" is generally not needed. Minus a Chrome bug, that is.

Update: January 13, 2019

In a Twitter conversation about list heuristics in Safari, James Craig shared insight into how Safari decides if a table is a layout table or a data table. It is a great look under the hood at how browsers have to deal with the code they must parse constantly.

Update: September 29, 2020

Léonie Watson has just posted How screen readers navigate data tables where she walks through a sample table to get some information, explaining each step, the keyboard commands, and the output. She also links to a video demonstration, which I have embedded below.

Watch How screen readers navigate data tables at YouTube.

Note that the table used in that demo has no scope attribute on either the row or column headers. NVDA handles it just fine.

Update: May 6, 2022

There is a bug in Chromium over support for the headers attribute that I genuinely hope someone fixes already: Issue 1081201: headers attribute ignored in tables resulting in an incorrect screen reader experience)

Update 20 May 2022: APG Relaunches

On Global Accessibility Awareness Day (GAAD) 2022, the APG relaunched and rebranded itself as an accessible pattern library:

I am thrilled this no longer looks like a standards doc and is much easier to navigate.

I am less thrilled this left a pile of 404s with the move, implies it is a ready-to-use pattern library, obfuscates the (watered down) warnings I fought so hard to get added, quietly hid some of its worst patterns with no acknowledgment in years-old issues, appears to have happened outside the W3C redesign project with Studio24, and introduced WCAG issues.



Are you really telling us that Blue is G and Green is B? :-)

glen; . Permalink
In response to glen. Reply

I gotta admit, I considered all sorts of data that was weird (including color splotches that were clearly wrong).


Hi Adrian,

Excellent post with the scenarios. How about interactive tables (tables with form elements / links in it)? Should they be Grid / Tables?

Do you have any suggestions for that?

Herin; . Permalink
In response to Herin. Reply

Herin, the answer to that depends on what you are doing. Some thoughts:

  • Links in a table are generally fine, unless every cell is a link, then it can get verbose for screen readers.
  • A table that looks and functions like Excel is best suited with the ARIA grid patterns.
  • If you have a table with one column that has a checkbox or radio button, that in itself is fine provided you give each one a unique and descriptive accessible name (which may be via aria-label or aria-labelledby.
  • Sortable columns will also need to be controls that themselves are accessible and live in the column header.

In short, make sure any controls you add to the table are accessible, that they do not make the table too verbose, and do not turn your table into Excel.


How can I make a accessible table with css flexbox? HTML Table specification is simply left behind, it doesn’t really have the flexibility to deal with today’s requirements. I would love to see a tutorial on achieving the same accessibility with Flexbox.

Nikolai; . Permalink
In response to Nikolai. Reply

Nikolai, you can make rows and columns behave like a table using ARIA. I have two posts that can help, one explaining how browsers break table semantics when their display property changes, and another showing what ARIA roles to apply to one of those tables (along with a function to do it for you).

You can apply the same logic to your flexbox table. However, for an accessible table it really needs to conform to the structure I outline above in this post. HTML tables are perfectly suited to that. I am curious what you consider today’s requirements, because the requirements for tables as way to mark up data on two axes has not changed.

Leave a Comment or Response

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>