This is a problem if you want whole table rows to be clickable. Luckily there's a (hacky) workaround.
Table of contents
Problem
I want whole table rows (<tr>
s) to be clickable.
I don't want to:
- Wrap the
<tr>
s in<a>
elements either because that wouldn't be semantic. (Not sure if this would even work; haven't tested.) - Use
onClick
on the<tr>
elements because that wouldn't be accessible. - Use an
<a>
element in each table cell (<td>
) because that's not the same thing and this wouldn't be accessible either (duplicate links).
So I use the ::after
pseudo-element trick
to expand a link's clickable area to cover the whole table row:
<table>
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Eye color</th>
<th>
<span class="visually-hidden">Read more</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>John</td>
<td>22</td>
<td>Blue</td>
<td>
<a aria-label="Read more about John" href="#">→</a>
</td>
</tr>
<!-- ... -->
</tbody>
</table>
<style>
tr {
position: relative;
}
a::after {
content: '';
inset: 0;
position: absolute;
/* Debug outline */
outline: 2px solid hotpink;
outline-offset: -3px;
}
</style>
Demo of the problem on Flems.io.
(This is a crude-ish example; see e.g. Don't Turn a Table into an ARIA Grid Just for a Clickable Row by Adrian Roselli for tips on accessibility.)
Works well e.g. in Firefox:
But Safari doesn't support position: relative
on table rows,
so the ::after
element is way too big – it covers the whole viewport:
In effect, clicking anywhere on the page (above the fold if the page is scrollable) triggers the link on the last table row!
Solution
Step 1: alternative for position: relative
tr {
- position: relative;
+ transform: translate(0);
}
I found this from a GitHub issue in the w3c/csswg-drafts repo: [css-position][css-tables] position doesn't apply on <tr>
either,
from a comment by Oriol Brufau.
Honza Kopecký commented in Oct 2023 that "both Safari and Chrome on iOS don't work and the hack doesn't help either," but in my testing (Jun 2024) the hack does work in Safari on iPadOS v17.4.1.
Two more relevant things that I found:
- WebKit bug report:
position: relative
doesn't work as expected on table row - Stack Overflow question: Bug in most browsers?: Ignoring position relative on 'tbody', 'tr' and 'td'?
Step 2: still too big
Now the links' clickable areas are smaller, but still too big in Safari; they are as tall as the whole table:
In effect, the last link's clickable area expands below the table, but otherwise the table rows are properly clickable.
I found a solution from a GitHub comment by Loz Calver under the aforementioned GitHub issue:
If your only issue is absolutely positioned elements overflowing the table-row (e.g. absolutely positioned anchors to make a full row clickable), another possible workaround is to use
tr { clip-path: inset(0); }
. It causes some strange paint side-effects in Safari, like borders sometimes disappearing, but it does at least clip child elements as expected.
So:
tr {
transform: translate(0);
+ clip-path: inset(0);
}
Step 3: focus outlines
Because of the clip-path
used in step 2,
make sure you use negative outline-offset
for the table rows' focus styles,
or the focus styles will also be clipped and invisible:
tr {
transform: translate(0);
clip-path: inset(0);
}
+ tr:focus-within {
+ outline: 2px solid hotpink;
+ outline-offset: -2px; /* Needs to be negative because of the `clip-path` above */
+ }
+ a:focus {
+ outline: 0; /* The focus styles are on the table row */
+ }
a::after {
content: '';
inset: 0;
position: absolute;
- /* Debug outline */
- outline: 2px solid hotpink;
- outline-offset: -3px;
}
Final code
tr {
/*
Safari doesn't support `position: relative` on `<tr>` elements,
but these two properties can be used as an alternative.
Source: https://mtsknn.fi/blog/relative-tr-in-safari/
*/
transform: translate(0);
clip-path: inset(0);
}
tr:focus-within {
outline: 2px solid hotpink;
outline-offset: -2px; /* Needs to be negative because of the `clip-path` above */
}
a:focus {
outline: 0; /* The focus styles are on the table row */
}
a::after {
content: '';
inset: 0;
position: absolute;
}
Demo of the final code on Flems.io.
Tested with:
- Edge v125 on Mac
- Firefox v126 on Mac
- Safari v16.6 on Mac
- Safari on iPadOS v17.4.1