3

When I select text across multiple flex element items in Safari, the selection background becomes invisible on some parts of the text.

Here are some screenshots of the difference between Firefox and Safari:

And here's a simple code sandbox to reproduce:Link

Did anyone encounter this problem before?

Matt Qafouri
  • 1,449
  • 2
  • 12
  • 26

1 Answers1

2

This is a documented Webkit/Safari bug affecting display: flex and display: grid.

As of this writing, the text selection bug appears to affect the first block-level leaf descendent of certain direct children of the flex or grid container, depending on certain layout properties (see Layout Fiddles below).

It's worth noting that no valid values of the user-select property will have any effect on the above-described bug, nor will use of the ::selection pseudo-element. It's also worth noting that table / display: table, inline-block, float, and columns layouts do not appear to be affected by this bug, so those could be viable alternatives for certain use cases. However, since flex and grid are arguably the most powerful tools for building layouts nowadays, here are a couple of workarounds that will still allow you to use those implementations:

Approach 1: Empty Block Element

Since the above-described bug causes the first block-level leaf of the second flex item in the OP's example to be un-selectable, this workaround simply adds an empty block element (in this case a div, but any element with a display value of block will work) above <p>invisible</p> which fixes the problem:

<div style={{ display: "flex" }}>
  <div style={{ marginRight: 12 }}>
    <p>blue</p>
    <p>blue</p>
  </div>
  <div>
    <div />
    <p>invisible</p>
    <p>blue</p>
  </div>
</div>

Updated code sandbox showing Approach 1 in action:

https://codesandbox.io/s/flamboyant-raman-6yq7m

Approach 2: ::before Pseudo-element

This fix uses the same concept as the first, except it uses a CSS pseudo-element instead of an empty div. For simplicity, it also applies this rule to all flex items, regardless of whether or not they might be affected.

.flexContainer {
  display: flex;
}

.flexItem::before {
  display: block;
  content: "";
}
<div class="flexContainer">
  <div class="flexItem">
    <p>blue</p>
    <p>blue</p>
  </div>
  <div class="flexItem">
    <p>invisible</p>
    <p>blue</p>
  </div>
</div>

Updated code sandbox showing Approach 2 in action:

https://codesandbox.io/s/sharp-andras-jv6x4?file=/src/styles.css

This approach is arguably more maintainable since you don't have to manage special-case child elements like you would with the first approach. One caveat is you will use up your one and only ::before pseudo-element on the flex item in case you were planning to use it for something else.

Safari-specific Fix

A compatibility fix meant for Safari that is benign in other browsers could be the ideal solution. This appears to be the case with both approaches mentioned above (at least with the latest Chrome and Firefox as of this writing, though a solid round of browser testing is always a good idea).

However, if you want to "contain the hack" so to speak then you could try using a device/user-agent sniffing Javascript library (e.g. react-device-detect if you're using React) and conditionally render workarounds accordingly.

Alternatively, if you end up going with Approach 2 then you could use Safari-specific CSS targeting to render the pseudo-elements only in Safari.

Layout Fiddles

In the fiddles below, I have extrapolated on the OP's original example showing how different flex and grid layouts affect which first leaf descendants do and do not show highlighted text. I have added additional flex items and have nested the first element of each direct child of the flex container to show that tree depth appears to be irrelevant.

Layout Fiddle
flex row Fiddle
flex row (wrap) Fiddle
flex column (wrap) Fiddle
grid Fiddle
Aaron Sarnat
  • 1,207
  • 8
  • 16
  • I need to use `flex` for my layout unfortunately – Just Jake Jul 26 '21 at 20:10
  • @JustJake See my updated answer with workaround that still uses `flex`. Also, thanks to whoever found the corresponding webkit bug and added the link to my answer. I looked but couldn't find it. – Aaron Sarnat Jul 27 '21 at 02:55
  • 1
    I know having an empty div seems hacky but what do you do when the browser rendering engine is broken? Seems you either live with the issue or you introduce something into the code that isn't ideal. Safari was a trailblazer 10-15 years ago, but honestly it's starting to feel like "the new IE." I swear all the weird browser compatibility bugs I come across nowadays are because of Safari. Sigh. – Aaron Sarnat Jul 27 '21 at 03:00
  • Added another solution using `:before` pseudo-element. – Aaron Sarnat Jul 27 '21 at 05:01
  • Added a table of fiddles that show different layouts and how the bug manifests in each. FWIW Approach #2 solves each use case pretty well, I think. – Aaron Sarnat Jul 27 '21 at 05:59
  • 1
    I added the link to the webkit bug. The solution I ended up using in my layout was to make the entire first-item a ::before pseudo-element, but the finding that you can add an empty one to work-around the issue in general is great, I think it's the best option to solve the bug without disrupting layout in general. Thanks for your help! – Just Jake Jul 27 '21 at 19:30
  • @JustJake thanks for adding the webkit bug link! And I'm glad that you found my workaround helpful. So.... I can haz bounty? :) – Aaron Sarnat Jul 28 '21 at 05:52
  • Added detail about elements affected being block-level specifically. Also added `display: table` and `float` to list of layouts unaffected. – Aaron Sarnat Jul 28 '21 at 06:01