Safari doesn't support position: relative on <tr> elements

Published on in CSS and Safari

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:

Screenshot of Firefox showing that the problem is not a problem: the links' debug outlines cover the table rows.

But Safari doesn't support position: relative on table rows, so the ::after element is way too big – it covers the whole viewport:

Screenshot of Safari showing the problem: the links' debug outlines cover 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:

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:

Screenshot of Safari showing that step 1 fixed the problem only partially: the links' debug outlines are still too big.

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