How to Create an Accessible Table with Clickable Rows

Tables on websites usually display static information in rows and columns. But what if you want to make the rows clickable, e.g., to show more information about the thing that the row is about? Your first thought might be to put click event handlers on the table rows (<tr>) to make them clickable. But you really shouldn't!

A table row with a click event handler doesn't magically turn into an accessible control. You won't be able to focus the row using a keyboard, let alone trigger the click event using the ENTER key. The purpose of the click event handler would be unclear to a screen reader user. So what should we do?

A woman touches the screen of a smartphone with her index finger. Photo: © Kampus Production / pexels.com

To create an accessible table with clickable rows, we simply take a <button> element and extend its reach to cover the whole row. My solution is inspired by Adrian Roselli's article “Don’t Turn a Table into an ARIA Grid Just for a Clickable Row”.

Demo: Table with Clickable Rows

I've created a CodePen demo that lists several Studio Ghibli movies in a tabular format. Each row can be clicked to open a dialog with more information about the movie:

Building the Accessible Table

The Ideal Solution

We add a <button> element in the last table cell of each row. The button's accessible name (“More Information”) together with the row heading (the <th> element in the row) inform screen reader users about the purpose of the control:

<tr> <th>Spirited Away</th> <td>2001</td> <td>2h 4m</td> <td> <button id="info-btn-1" aria-label="More Information" type="button"> <span>/* icon */</span> </button> </td> </tr>

The button is keyboard operable by default. All we need to do is to visually convey that the whole table row is focused. We can use the :focus-within CSS pseudo-class:

table tbody tr { &:focus-within, &:hover { outline: 0.188rem solid black; outline-offset: -0.313rem; } button:focus { outline: none; } }

This way, the table row is highlighted on hover or when the button within the row receives focus.

Finally, we want to trigger the button when the user clicks on the table row. To achieve this, we use the ::after pseudo-element together with the inset CSS property:

table tbody tr { position: relative; button::after { content: ""; position: absolute; inset: 0; } }

The pseudo-element is absolutely positioned relative to its parent <tr> element and covers the whole table row thanks to inset: 0. Now, a click on the table row will count as a click on the button within. Awesome! 🤩

There's only one problem: It doesn't work in Safari (at the moment). The position: relative definition for the <tr> element is simply ignored. 🤬

The Safari Supported Version

As long as the WebKit bug isn't fixed, we need a workaround. I've created an alternative CodePen demo that also works in Safari:

The solution is a bit hacky, but it works. Instead of using the table row as the relative anchor, we position the pseudo-element relative to its parent <td> element. Take a look:

table { overflow: clip; } table tbody tr { td:has(button) { position: relative; } button::after { content: ""; position: absolute; inset: 0; left: auto; width: 100vw; } }

We revert the pseudo-element's left property to auto and set its width to 100% of the viewport width. In addition, we set overflow: clip on the table to limit the reach of each button to the actual width of the table row.

Conclusion

As you can see, creating an accessible table with clickable rows isn't that hard. Always keep in mind that not all of your users use a mouse or a touch device. Always test with keyboard and screen readers as well. 😉

Posted on