Strange quirks of Safari's dark color scheme handling

For the longest time I've been irked by how my site's underlay and scroll bars weren't dark when I was in dark mode in desktop Safari.

For a visual of what I'm talking about, open this example site in a new private window in desktop Safari (version 15.5). If you open it when you have your OS color scheme preference set to dark, you will see that the site's content does respect the user's color preference (the background color is black, text is white), but the scroll bar does not respect the user's color preference (it's white when it should be dark gray).

You'll also see the underlay (the area under your site that you can see briefly if you use a trackpad and scroll up or down past the edges of the site) also does not respect the user's color preference.

So, what gives? Well, I found two ways to solve it, but I'm still not entirely sure why it happens (more on that later). Let's look at the solutions.

Solution 1 - Reduce render-blocking CSS

The first solution I found was that by reducing the other CSS on the page that blocks rendering, the behavior went away.

Here is a minimal snippet of what the page with the problematic behavior looks like:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <style>
      :root {
        --text: #000;
        --background: #fff;
      }

      @media (prefers-color-scheme: dark) {
        :root {
          --text: #fff;
          --background: #000;
        }
      }

      @font-face {
        font-family: "Fira Mono";
        src: url("assets/font/fira-mono-700.woff2") format("woff2");
        font-weight: 700;
        font-display: swap;
      }

      @font-face {
        font-family: "Fira Mono";
        src: url("assets/font/fira-mono-400.woff2") format("woff2");
        font-weight: 400;
        font-display: swap;
      }

      body {
        font-family: "Fira Mono", monospace;
        color: var(--text);
        background: var(--background);
      }
    </style>
  </head>
</html>

You'll see that the page handles the text and background color based on what the user's color scheme preference is. The page also makes use of commonly used @font-face CSS rule to load the Fira Mono font.

It turns out that if you remove the @font-face rules, Safari no longer has the issue. If you open this example page in a new private window you'll see it works now.

There's a lot of great info about how to optimise web font loading, but there's not a lot about the interaction between render-blocking and when the browser decides to evaluate color scheme preferences.

So, why does removing @font-face make the browser respect the user's color preference for it's underlays and scroll bars? At this point in time the answer is: I don't know, and I don't know enough about what the browser engine is doing to debug what is probably a race condition. I haven't successfully compiled WebKit yet either (despite multiple attempts), so that's another blocker.

Although I was able to reproduce this, it's not a rock-solid conclusion, nor generally applicable since every site's usage of CSS is different. It's a data point that may prove useful though to someone (or my future self) looking to dig deeper.

Solution 2 - Use the theme-color meta tag

The second solution I found was to add the theme-color meta tag to the page:

<meta name="theme-color" content="#000000" />

If you want to get a little fancy, you can use the media attribute to match the user's color scheme preference:

<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)" />

The thing is, this by itself doesn't fix it in Safari. Due to Apple's choice to limit the spectrum of valid theme colors, Safari does not allow black (#000), some reds, yellows or greens. This stack overflow thread has some great info, and someone named Roger Lee made a neat tool that allows you to check what colors are valid.

The most frustrating part about this is that even the colors Apple shows in their WWDC session that announces this feature don't work, and I can't find any documentation anywhere that tells me what colors are and are not valid.

I did find one way to circumvent the #000 problem, though:

<meta name="theme-color" content="#000001" />

It seems like Safari checks for #000 strictly, so #000001 works just fine. You can see it working if you open this example page in a new private window.

At the end of the rabbit hole

This was a journey that didn't feel particularly fruitful, discounting the fact that the behavior now works as expected with the changes mentioned.

If for some reason you'd like to explore this issue more yourself, feel free to check out the repo. Or, maybe don't, there are much more gratifying things to do. Either way, hope this was useful to you (or future me) in some way!


Thanks for reading! Go home for more notes.