Skip to main content
Photography of Alvaro Montoro being a doofus
Alvaro Montoro

Honorary Tortoise

a table

Making tables more accessible with CSS

a11y css

Many things can be done to make an HTML table accessible: add a <caption>, use semantic HTML (<thead>, <tbody>, <tfoot>...), associate date with the right headers (using role, id and headers)...

Most of those changes will go on the HTML side, but some things can be done exclusively on CSS to improve the accessibility and usability of tables.

Let's see them starting with an example:

      <th>Col1</th> <th>Col2</th> <th>Col3</th>
      <td>ABC</td> <td>DEF</td> <td>GHI</td>
      <td>JKL</td> <td>MNO</td> <td>PQR</td>
      <td>STU</td> <td>VWX</td> <td>YZ.</td>
      <td>123</td> <td>456</td> <td>789</td>

That code will generate a table like this one on most browsers:

Screenshot of a table with 3 rows and 3 columns. There are no styles

Without styles, the table content is clogged and can be confusing.

This is a table with no styles whatsoever. And needless to say, it looks terrible. Many issues need to be addressed to improve accessibility and usability in general. Sighted users (even ones with 20/20 vision) will have many problems:

  • It's tough to differentiate table head rows from the rest of the rows (even with the bold from the <th>).
  • Content within the rows is too close together, and it may not be easy to see where a column ends, and the next one starts.
  • In bigger tables, users could "lose track" of the line and jump between lines.
  • It's impossible to differentiate between rows in the table body and rows in the table footer.

This post will review some easy fixes for those issues and how they can be implemented just on the CSS side.

1. Add spacing between the content

Adding space will help unclutter and unclog the table. We can achieve this by adding padding to the table cells –and don't forget the <th> too:

th, td {
  padding: 0.5rem 1rem;

The original table with this change will look like this:

Same table as before, but now the columns are separated

Now the columns are clearly differentiated

Note that the padding values could be adjusted to make the separation bigger or smaller, but at this point, the table columns are clearly differentiated and obvious, which was our goal.

To better differentiate the content, we could have gone for a different approach adding borders in the table –and that would have addressed several of the issues noted above–, but personally, the result is still subpar and would require some of the suggestions below to make it look better.

2. Add a visual separation between table regions

The only visual indication that the first row is a header is that the text is written in bold letters. This happens because we are using <th>. If he used <td> instead, it would be impossible to say.

The same thing happens with the <tfoot>; it is visually impossible to know if the last row is a table footer or another row in the <tbody>.

One thing that can be done is adding a line below the <thead> and above the <tfoot>. We could do that by applying a border to the head and foot cells, but that could lead to (easily solvable) problems.

Another option is using a box-shadow that will be applied to the thead and tbody elements. Like this:

thead {
  box-shadow: inset 0 -2px;

tfoot {
  box-shadow: inset 0 2px;

Now you can clearly distinguish what the head, the body, and the footer in our table are:

Table with lines between the first and second rows, and between the last and second to last rows

Each section of the table is defined

While the sections are easily identifiable, if the table had many rows and columns, users reading data horizontally may jump from line to line and lose track of where they are.

3. Add a distinction between consecutive rows

To avoid this jumping from line to line and from row to row, we can change the row color alternatively. To do so, we can target odd rows using :nth-child(odd) and even rows by using :nth-child(even):

table > tr:nth-child(even),
tbody tr:nth-child(even) {
  background: rgba(0,0,0,0.05);

table > tr:nth-child(odd),
tbody tr:nth-child(odd) {
  box-shadow: 0 -1px rgba(0,0,0,0.1), 0 1px rgba(0,0,0,0.1);

As you may have noticed, if you applied the code above, it leaves a small spacing between rows that doesn't look great. To avoid it, we can collapse the borders:

table, thead, tbody, tfoot, tr, td, th {
  border-collapse: collapse;

Which will generate this table:

Table with borders after table head, and before table footer, also the lines alternate colors

Alternating rows make content easier to follow

Now our table has clearly separated sections, and the alternating rows make the content easier to follow.

4. Add highlight to active/hovered row

One final touch would be highlighting the row that is currently "active" or, more specifically, where the mouse is over.

We do by applying a different background on the :hover state of each row:

table > tr:hover,
tbody tr:hover {
  background: #bdf;

Now we have the same table as before, but when we move the mouse over each row, it is displayed in a shade of blue:

Alt Text

And with this one, we are done. As you may have noticed, these tips and tricks are not specific to accessibility, but more related to the table's visualization and its usability.

They will impact how sighted, and low-vision users interact and with the tables. And in that sense, they are important for accessibility too.

Create tables in which the data is clearly sorted and distinguishable.

Article originally published on