Skip to content

Dark Mode

Adding dark mode to your Astro project.

Adding dark mode to your Astro project.

src/layouts/index.astro
---
import "../styles/globals.css";
---
<script is:inline>
const getThemePreference = () => {
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
return localStorage.getItem("theme");
}
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
};
const isDark = getThemePreference() === "dark";
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
if (typeof localStorage !== "undefined") {
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
}
</script>
<html lang="en">
<body>
<h1>Astro</h1>
</body>
</html>
src/components/ThemeSwitcher.astro
---
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@bejamas/ui/components/select";
import { SunIcon, MoonIcon, LaptopIcon } from "@lucide/astro";
---
<div class="relative text-sm">
<div class="absolute z-10 left-3 top-2.5 pointer-events-none">
<SunIcon class="size-4 dark:hidden" />
<MoonIcon class="size-4 hidden dark:block" />
</div>
<Select id="theme-select" class="w-28">
<SelectTrigger class="pl-9">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="auto">Auto</SelectItem>
</SelectContent>
</Select>
</div>
<script is:inline>
// @ts-nocheck
const storageKey = "starlight-theme";
const themeMetaMedias = [
"(prefers-color-scheme: light)",
"(prefers-color-scheme: dark)",
];
const themeColors = {
light: "#ffffff",
dark: "#0f171f",
};
const parseTheme = (theme) =>
theme === "auto" || theme === "dark" || theme === "light" ? theme : "auto";
const loadTheme = () => {
try {
if (typeof localStorage !== "undefined") {
return parseTheme(localStorage.getItem(storageKey));
}
} catch {}
return "auto";
};
function storeTheme(theme) {
try {
if (typeof localStorage !== "undefined") {
localStorage.setItem(
storageKey,
theme === "light" || theme === "dark" ? theme : "",
);
}
} catch {}
}
function ensureThemeMeta(head, media) {
const baseSelector = `meta[name="theme-color"][media="${media}"]`;
let meta = head.querySelector(`${baseSelector}[data-theme-managed]`);
if (meta instanceof HTMLMetaElement) return meta;
meta = head.querySelector(baseSelector);
if (meta instanceof HTMLMetaElement) {
meta.setAttribute("data-theme-managed", "");
return meta;
}
meta = document.createElement("meta");
meta.setAttribute("name", "theme-color");
meta.setAttribute("media", media);
meta.setAttribute("data-theme-managed", "");
head.appendChild(meta);
return meta;
}
function syncThemeColor(theme) {
const color = themeColors[theme];
const head = document.head;
if (!color || !head) return;
themeMetaMedias.forEach((media) => {
const meta = ensureThemeMeta(head, media);
meta.setAttribute("content", color);
});
}
const getPreferredColorScheme = () =>
matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
function applyTheme(theme) {
const effective = theme === "auto" ? getPreferredColorScheme() : theme;
document.documentElement.dataset.theme = effective;
document.documentElement.classList.remove("light", "dark");
document.documentElement.classList.add(effective);
syncThemeColor(effective);
storeTheme(theme);
const select = document.getElementById("theme-select");
if (select instanceof HTMLSelectElement) select.value = theme;
}
// Initialize
applyTheme(loadTheme());
// UI: select changes
const select = document.getElementById("theme-select");
if (select) {
select.addEventListener("select:change", (e) => {
const { value } = e.detail;
applyTheme(parseTheme(value));
});
}
window.toggleTheme = (theme) => applyTheme(parseTheme(theme));
// React to system scheme changes while in 'auto'
matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => {
if (loadTheme() === "auto") applyTheme("auto");
});
</script>

Place a mode toggle on your site to toggle between light and dark mode.

src/pages/index.astro
---
import "../styles/globals.css";
import ThemeSwitcher from "@/components/ThemeSwitcher.astro";
---
<!-- Inline script -->
<html lang="en">
<body>
<h1>Astro</h1>
<ThemeSwitcher client:load />
</body>
</html>