Preventing FOUC

Shubham Gulati

When implementing dark mode in a web application, one of the most jarring experiences for a user is the FOUC (Flash of Unstyled Content). It happens when the browser renders the default light theme for a split second before the JavaScript execution realizes the user prefers dark mode and flips the switch.

The best way to handle this is to treat it as a “blocking” priority. We essentially want the browser to figure out the color palette before it even thinks about drawing a single pixel of the body.

Inline Blocking Script

To do this, the simplest solution is to add an inline blocking script in the <head> of the document to ensure dark class is applied to the <html> element immediately. This script checks for a saved preference, with key theme, in local storage and falls back to the system’s color scheme if no manual choice is found.

<script is:inline>
  const theme =
    localStorage.getItem("theme") ||
    (window.matchMedia("(prefers-color-scheme: dark)").matches
      ? "dark"
      : "light");
  document.documentElement.classList.toggle("dark", theme === "dark");
  window
    .matchMedia("(prefers-color-scheme: dark)")
    .addEventListener("change", (e) => {
      if (!localStorage.getItem("theme")) {
        document.documentElement.classList.toggle("dark", e.matches);
      }
    });
</script>

Why this works

It’s a small piece of code, but it does a few things very well:

  • It relies entirely on native browser APIs. No extra bytes for a library that just checks a string.

  • Astro’s is:inline directive is the secret sauce. It tells Astro to keep the script exactly where it is in the HTML, rather than bundling or deferring it. For preventing a flash, that immediacy is everything.

  • The event listener is always listening to the system theme preference and if the system theme settings change while the site is open, the UI updates instantly as long as theme is not manually overridden.

  • It respects the theme key in local storage, so once a user makes a choice, it sticks.

A simple addition, but works well. Just what I was looking for.

Cheers!