Media attributes and you(r network requests)

Who doesn't love falling down rabbit hole while checking their assumptions about CSS and network requests? Add in some research, and you've got yourself a blog post goin'.

While surveying my Twitter timeline this morning, wondering what fresh horrors awaited me, this blog post about FOUT and dark themes in Gatsby/Next sites. Neither Gatsby nor Next are parts of any of my stacks at the moment, but Daniel's reasoning is pretty solid.

I've been thinking about dark themes lately—still need to make one for this here site—and Daniel's post sent me down that rabbit hole again.

One of my personal hurdles is keeping my CSS as minimal as possible 1, and including unnecessary dark theme-specific CSS (or light-themed, depending on your perspective; we do not theme-shame here) always struck me as bloat. Sure, bloat that makes for a better user experience, but bloat nevertheless.

My first thought to solve this was putting splitting the theme-specific CSS into a separate file, and slapping a media attribute on it.

EXAMPLE CODE, GO!

<!DOCTYPE html>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tinkering with media attributes, y'all</title>
<link rel="stylesheet" href="default.css" />
<link rel="stylesheet" href="dark.css" media="(prefers-color-scheme: dark)" />
<p>hi there</p>

default.css

:root {
    --body-bg-color: white;
    --body-text-color: black;
}

body {
    background: var(--body-bg-color);
    color: var(--body-text-color);
}

dark.css

:root {
    --body-bg-color: black;
    --body-text-color: white;
}

If your OS is set to use light themes, you'll see black text on a white background, and if your OS is set to use dark themes, you'll see white text on a black background.

Sometimes I forget that the media attribute accepts any valid media query, and it operates like @media block rules in your CSS. But that's what it does. It's pretty neat.

Now, the assumption I made here is that placing dark.css in a separate file could be a performance gain because, for example, the browser would see (prefers-color-scheme: dark) and not request the file if the visitor's OS were set to use light themes.

That assumption was and is very, very wrong.

I opened the network tab in dev tools and saw that in Firefox, Chrome, Edge, and even IE, dark.css was requested regardless of the OS settings. Just to make sure I wasn't missing some typo, I changed media to print and got the same results, even though the styles weren't applied to the document.

I could've just accepted that as The Way and moved on, but reader, you are here for a blog post and that would not be very fun. So, LET'S GO TO THE SPEC! Specifically, HTML 5.2, section 4.2, with my own add emphasis.

If the link is a hyperlink then the media attribute is purely advisory, and describes for which media the document in question was designed.

However, if the link is an external resource link, then the media attribute is prescriptive. The user agent must apply the external resource when the media attribute's value matches the environment and the other relevant conditions apply, and must not apply it otherwise.

The default, if the media attribute is omitted, is "all", meaning that by default links apply to all media.

Note: The external resource might have further restrictions defined within that limit its applicability. For example, a CSS style sheet might have some @media blocks. This specification does not override such further restrictions or requirements.

HTML 5.2: 4.2.4.1 Processing the media attribute

That second paragraph contains the answer. The browser must apply the styles in that resource when the media query matches the environment, and the thing we have to remember is that the environment can change at any time, for any number of reasons.

In my example code, dark.css is only applied if (prefers-color-scheme: dark) evaluates to true, but you can change your OS settings at any time, and the document styles should update accordingly (and in my opinion, with little to no effort on the part of you, the visitor).

In the case of, say, setting the value of media to print, the browser doesn't know when (or if) you're going to print that page, so it needs to download it just in case.2

If we set media to (min-width: 500px), the viewport may or may not be 500px wide when the page is loaded, or it may scale up or down after the fact, so the browser needs to be able to apply the styles when appropriate.

I'll keep this list of scenarios at three for brevity (there's a lot of possible media query situations) and because three is an aesthetically please number for me. We all win.

The point is that the browser downloads the resource even if media doesn't match the environment because it could match at some point. In other words, using media is not a performance win. Womp womp.

A little more about that media attribute

Let's look back at the note included in section 4.2.4.1 of the spec: "The external resource might have further restrictions defined within that limit its applicability. For example, a CSS style sheet might have some @media blocks. This specification does not override such further restrictions or requirements."

So the process goes:

  1. The browser downloads the resource regardless of whether media matches
  2. Those styles are applied according to the outcome of media
  3. But any media queries found inside that resource are also evaluated and applied accordingly, regardless of the media attribute.

Pretty straight forward if you ask me—but no one did, so of course this isn't how it works.

The way step 3 actually happens is that the subsequent media queries in the linked resource are effectively nested within the value defined in media (and remember, its default value is all so if it isn't explicitly defined, all of your linked media queries are then nested within an implied @media all rule).

For example, if you set media to (min-width: 500px) and included a (min-width: 600px) media query, the styles defined in the latter would be applied because both conditions were met.

However, if you set media to (width: 500px) and included a (min-width: 600px) media query, the latter would never be applied because the viewport could not be both 500 pixels and 600 pixels wide simultaneously.

Ok, so what are the takeaways?

  1. media doesn't prevent network requests
  2. Media queries in a linked stylesheet are nested within the condition in media (with a default of all)
  3. It's ok to put your theme-specific CSS in with the rest of your CSS and work to keep it all streamlined

Footnotes

  1. "lol," said Sisyphus

  2. I don't the specifics of how a browser's printing UI works, but I am almost positive it is not a separate browser tab and would not trigger new network requests when the UI is activated.