Cross-browser approach to .visually-hidden on table headings
tl;dr Using .visually-hidden
on table headings breaks built-in association between column headings and cell content when using VoiceOver with Firefox or Safari, while .visually-hidden
crashes Opera if used on <thead>
! The workaround is to wrap the content you wish to hide in a <span class="visually-hidden">
.
A few weeks ago I was working on an opening hours table for a restaurant. The visual design didn't include table headings so I decided to add visually hidden headings to make the table accessible. I used slightly expanded .visually-hidden
class from Heydon Pickering's Inclusive Components to achieve this. You can find the .visually-hidden
class I use at the end of the article.
Initially I put the .visually-hidden
class on <thead>
but VoiceOver on Firefox didn't register column headings, while traversing the table on Opera caused it to crash! More on Opera crash later. So then I tried moving .visually-hidden
class downstream to <tr>
but that broke the built-in association between column heading and column cells in Firefox while VoiceOver on Safari wouldn't even announce column headings. I continued moving .visually-hidden
downstream to <th>
but that also broke association between column headings and cell content in both Firefox and Safari.
Upon investigating .visually-hidden
class I found the culprit: position: absolute;
. I tried replacing it with float: left;
but it seems that any technique that takes table elements out of normal flow breaks their accessibility. The cross-browser solution I came up with is to wrap content you wish to hide with a <span class="visually-hidden">
.
Note: using .visually-hidden
on Chrome and Edgium with VoiceOver produced no issue on any table-related element.
Opera crashing was surprising, even more so considering all it took was some CSS. The exact four CSS properties that cause the crash are:
width: 1px;
height: 1px;
overflow: hidden;
position: absolute; /* or float: left; */
I set up an example page that crashes Opera using this approach, which I submitted alongside a bug report to Opera. Unfortunately they don't have a public bug tracking system 🤷♂️ but at least they shared the bug report ID with me - DNAWIZ-89553 - which we can use to take a look in the changelogs to see if it was fixed already.
Update: the bug seems fixed 🎉 in the very next build even though the changelog doesn't mention the bug.
.visually-hidden:not(:focus):not(:active) {
width: 1px !important;
height: 1px !important;
padding: 0 !important;
border: 0 !important;
overflow: hidden !important;
position: absolute !important;
white-space: nowrap !important;
clip: rect(1px 1px 1px 1px) !important; /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px) !important;
clip-path: inset(50%) !important;
}