System based dark mode in Astro

There are two ways to handle themes: you can either detect the user’s OS preference or let them toggle manually. In this case, I went with the system-based option.

Step 1: Detect the system preference

<script is:inline>
  const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
  if (prefersDark) {
    document.documentElement.classList.add("dark");
  }
</script>

Adding this script inside the <head> makes sure the correct theme loads before anything shows up on screen. This prevents the awkard flash as it switches between light and dark.

That works, but if a user changes their OS theme, the site won’t update automatically. You’d have to refresh the page to see the new theme, which isn’t ideal.

Step 2: React to live theme changes

To fix that, we can listen for live theme changes. This way, the site updates instantly when the system theme changes, no reload needed. Just add this script into your main layout:

<script is:inline>
  const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");

  function applyTheme(e) {
    if (e.matches) {
      document.documentElement.classList.add("dark");
    } else {
      document.documentElement.classList.remove("dark");
    }
  }

  // Initial set
  applyTheme(mediaQuery);
  // Watch for changes
  mediaQuery.addEventListener("change", applyTheme);
</script>

Step 3: Set up your CSS

After that, tell the browser your site supports both light and dark themes:

:root {
  color-scheme: light dark;
}

Finally, add some CSS variables for both modes:

:root {
  color-scheme: light dark;
  /* css variables ... */
}
.dark {
  color-scheme: dark;
  /* css dark mode variables */
}

And that was pretty much it. No external libraries, no flicker, just clean native support.

258 words

© 2023. All rights reserved.