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.