Hey, It’s Still OK to Use Tables
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:
- Tables displayed via images, usually with useless alternative text,
- Tables assembled from the flammable tinder of
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
- ARIA Patterns
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.
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:
- Continue to use
- Add the
scopeattribute using the values
Conveniently, the WCAG technique offers a sample table, which I recreated on CodePen and also embedded below.
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.
- For every
<th>, give it an
idattribute (and a unique value, of course).
- Every cell then gets a
headersattribute that points to the
idof the header cell you want it to use.
Once again, the technique offers a sample table, which I recreated on CodePen and also embedded below.
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
ARIA has a few roles that define and describe data grids. The definitions that follow come from ARIA 1.1 (these are truncated).
- A composite
widgetcontaining 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.
- A cell containing header information for a row in a grid.
- A cell containing header information for a column.
- A row of cells in a tabular container.
- 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.
Since developers were applying
grid roles to what should be regular tabular data, ARIA 1.1 added the following role in response:
tablerole 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
Authors SHOULD prefer the use of the host language’s semantics for table whenever possible, such as the HTML
If it’s not a widget, don’t use
role="table". Ideally you do this by just using
<table>, because then you can skip the
<table> has that role implied.
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.
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.
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:
gridis a composite widget so it:
- Always contains multiple focusable elements.
- Has only one focusable element in the page tab sequence.
- Requires the author to provide code that manages focus movement inside it.
- For a
table, all focusable elements contained in a table are included in the page tab sequence.
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
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.
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.
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.
<table>s for tabular data.
- Don’t use ARIA grid roles unless you are making a spreadsheet (or something like 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.
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:
Want to use #ARIA to make your web apps more #accessible, yet tired of slogging through a LONG doc? Good news: ARIA Authoring Practices Guide (APG) is redesigned as shorter pages!w3.org/WAI/ARIA/apg/
(related email: lists.w3.org/Archives/Public/public-wai-…) #a11y #ARIAapg #GAAD #WebApps
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? :-)
I gotta admit, I considered all sorts of data that was weird (including color splotches that were clearly wrong).
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, 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
- 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, 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
displayproperty 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 considertoday’s requirements, because the requirements for tables as way to mark up data on two axes has not changed.