Don’t Turn a Table into an ARIA Grid Just for a Clickable Row
Again, title says it all. However, there is an equally bad opposite approach you might be tempted to use, so let me clarify:
- Don’t use ARIA
grid
roles simply to make rows clickable in a table, and - Don’t put click handlers on table rows (
<tr>
s) to make them clickable.
Step 1
Use a checkbox for each row. It’s a clear visual cue most users understand. You can even put a checkbox above and/or below the table to toggle all rows as selected or not. The <label>
element will do all the work for us.
If you are trying this technique with HTML buttons then the accessible name requires ARIA, but I outline how straightforward this is in my post Uniquely Labeling Fields in a Table.
Step 2
If you are insistent that the user can click anywhere on the row to check the checkbox, you can do that without JavaScript (and definitely without ARIA grid
roles). I show how to do it in my post Block Links, Cards, Clickable Regions, Rows, Etc..
That post does not use a checkbox in each row, but instead uses links and buttons. The approach is still the same, except we rely on the <label>
instead of ARIA.
Step 3
Peruse these to understand why and how I got to this place:
- Hey, It’s Still OK to Use Tables, which explains why you want to avoid ARIA
grid
roles (unless you are rebuilding Excel). - ARIA Grid As an Anti-Pattern walks through the challenges with ARIA grid off-label use.
- The redundant click event section in Heydon Pickering’s Cards pattern has script to enhance my pattern to allow users to select text in the clickable rows without immediately triggering the click.
Example
I made an example. If the embed below is not working, visit it directly.
The important bits of the CSS:
th {
position: relative;
}
th > label::after {
content: "";
display: block;
position: absolute;
inset: 0;
}
tr:hover, tr:focus-within {
outline: .5em solid rgba(0,0,255,.25);
}
th label:hover::after, th label:focus::after {
background-color: rgba(255,255,0,.1);
}
/* Duplicated style until Firefox supports :has() */
td:has(input:focus) + th label::after {
background-color: rgba(255,255,0,.1);
}
/* Width is here or else Safari honors nothing. */
th > label::after {
width: 92vw;
}
Adjust your visual styles as appropriate.
Firefox will support :has()
in 121, so you can delete that redundant style when your audience is current.
Without the width
on the CSS generated content (label::after
), Safari will not provide a box to allow the fake row click. I am unable to find any bugs related to this and none of the width
keywords does the trick. So adjust that 92vw
value as you see fit, potentially plugging it with one based on a known or calculated size (ugh, Safari).
Update: 3 November 2023
Michal Čaplygin dug up the bugs that informed some of what I am trying in this demo:
Neat trick indeed. Is there a reason (some compat bug perhaps) for insetting the pseudo element in scope of table *cell* instead of entire table *row*? (Moving position: relative from `th` to `tr` would make clickable even the area around the checkbox in the first `td`…)
…aaand to answer myself, it seems there is really a bug in WebKit (at least version shipped with PlayWright) so table row cannot become offsetParent. OMG! Similar bug have been for a decade in Firefox, but it is also a decade since it was fixed: bugzilla.mozilla.org/show_bug.cgi?id=63895
…and in Chromium was tr { position: relative } fixed [checks notes] two years ago? Could it be? I would have bet it worked there earlier, maybe even before Firefox. Interesting. (And sad.) bugs.chromium.org/p/chromium/issues/detail?id=417223
And most fitting bug report for the WebKit ("`position: relative` doesn't work as expected on table row") is only about a year old? This cannot be real. bugs.webkit.org/show_bug.cgi?id=240961
12 Comments
What if the main action is behind a more-menu button with additional actions for this row? Is it then okay to put a click handler on the row while keyboard/screen reader users still have an accessible way to trigger the action?
In response to .So two interactive bits in one row? Still not ok to use a click handler on the row. Limit the width of the CSS generated box to ensure the other control is not covered. Ideally put that secondary control at the far end of the row.
In response to .Sandro, I just re-read your comment. You have a disclosure or menu trigger in a cell in your row. I would probably not make the entire row clickable in that context and I still would not put a click handler on the row.
In response to .OK, thanks
Great article, however (and it’s a bit however and I do not lie), if you test the CodePen example with a screen reader the title column in the table is empty, except the first row (excluding the column header).
I haven’t looked at the code but there is something off there.
In response to .Birkir, the embedded example is not CodePen, though that should not matter. I am not near a computer today (nor my Mac for a week or two). Can you tell me the screen reader and browser combo so I can queue it for testing later?
Jaws 2023 + Chrome (whatever the latest version is), no rush
In response to .The JAWS/Chrome issue goes away when there is any other element in there (NVDA does not have this problem with Chrome, so I will have to do deeper debugging later to see if this is a JAWS bug or not). Because I am on the road, I made a quick CodePen to demonstrate a fix, where I add a
<wbr>
as the last node. It is a hack.
In response to .Birkir, I filed a JAWS issue: #783 Row headers with labels announce as ‘blank’ with table navigation
Cool! Yes, the example works now.
Another hack I tried out, and it works is to use aria-labelledby instead:
John Johnson
The checkbox reads “Select John Johnson” and the header cell reads as expected.
Sorry, HTML code does not come through, trying Markdown style comment
“`
id=”c11″ aria-label=”select”>
John Johnson
“`
If this doesn’t work, basically the idea is to use a theckbox input element with
id=”foo” aria-label=”select” and aria-labelledby=”foo hoo”
Then put id=”hoo” on the table cell element with the checkbox label.
If the text of that cell is “john” the checkbox screen reader label now reads “select John”
In response to .Yeah, my comment form strips most HTML (it only allows the elements listed after the comment field) and it does not support markdown. Escaping HTML the brackets (
<
&>
) is the best way to show HTML code snippets here.That said, I appreciate you trying another method. Given my aversion to
aria-label
(primarily since it does not auto-translate but also becausearia-label
is wonky for header cells) and since adding it makes for a more verbose accName than I want, I will stick with the arbitrary non-rendering HTML element.
Leave a Comment or Response