Last week, as I was auditing a website for accessibility, I came across a table that was completely coded in divs. ????

This was a pricing table that was styled quite nicely, and visually looked like a table. To a sighted person, nothing would be wrong because they could easily scan across rows and down columns to see what feature applies to which price option.

Why Tables Shouldn’t Be Built With Divs
To understand why you (or the plugins you choose) shouldn’t build tables with divs, it’s important to understand how a screen reader user experiences an HTML table.
Screen readers allow for special interaction when navigating through a correctly coded table to help the user more quickly find the information they are looking for and interpret what they are hearing.
- With a screen reader, you can use the up and down arrows and right and left arrows to skip through cells quickly.
- When you enter a table cell, the screen reader will read out the row and column number and the headings for that row and column, in addition to the data that is the cell. This provides extra context making the table more understandable.
In the table shown above, coded with divs, you can only use the left and right arrow keys. That means there is no way to go down a single column and hear all the features included with that specific plan.
It also fails to give context in each cell, so you only hear the plan names (Free, Pro, Team, Enterprise) once, and you somehow have to count your tab-stops to try and determine which plan a specific benefit applies to.
With a pricing table coded in this way, it would be impossible for a user to compare and choose a pricing plan without asking a sighted person to help them.
The Right Way to Code a Table
The right way to code a table is to…use HTML <table> tags. Sometimes the most simple answer is the best solution, and this is definitely one of these times. Using <table> tags also has the added benefit of saving you from having to write a CSS to position divs in a tabular layout.
Here are the best practices to follow when coding an accessible table:
- Use semantic HTML: Use HTML tags that clearly define the structure and purpose of each element in the table. For example, use <table> to define the table, <thead> to define the table header, <tbody> to define the table body, and <th> to define table headers.
- If you have multiple headers on your table, for both columns and rows, ensure you’re using <th> tags for the row headers too (not just the column headers, a common mistake), and use the scope attribute to indicate what the header applies to.
- Make the table responsive and ensure it can adapt to different screen sizes, both mobile phones and large monitors, and doesn’t extend off the page if the website is zoomed in to 200% or 400%.
Here is an example code for the pricing table above, with headers that are scoped to apply across rows and columns.
In this example, the first row is all <th> elements have a scope of “col” and define the column headers. The first cell in each row uses a <th> element with a scope of “row” to define the row headers.
If you don’t want to fill in the top left cell in your header because it’s a placeholder, you can hide it from screen reader users with aria-hidden like this:
<th aria-hidden="true"></th>
That will stop them from encountering an empty first cell.
Tables in WordPress
A note of caution – many of the existing WordPress table plugins have accessibility issues. If you’re trying to select a table plugin, the first thing you can do is inspect the code of the plugin’s demo. If the plugin is not using HTML <table> tags, it’s easy to rule out the plugin.
If the plugin includes filters, searching, or sorting, you’ll want to test the demo with a screen reader to make sure that it properly announces all of the changes when things are searched and filtered. Many of the tables with irregular headers opens a new window top WordPress plugins for building tables do not handle this advanced functionality well.
Also worth noting is that the WordPress core table block doesn’t have support for adding row table headers, so it should only be used for making very basic tables. Our approach for very complex tables, like the pricing table above, is to build an ACF block with repeaters, which allows us to have full control over the frontend output but gives the content creator a “builder” experience for the table.
Additional Resources
Join the Conversation!
There's a dedicated thread on this post inside of The Admin Bar community. Join in on the conversation, ask questions, and learn more!
Group Thread