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?
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