Skip to content

Instantly share code, notes, and snippets.

@fairjm
Last active December 25, 2025 18:57
Show Gist options
  • Select an option

  • Save fairjm/ad3f92f7bd1a3bba73461eeadb39dd47 to your computer and use it in GitHub Desktop.

Select an option

Save fairjm/ad3f92f7bd1a3bba73461eeadb39dd47 to your computer and use it in GitHub Desktop.
Astro + Shadcn UI: Persistent Theme Toggle with View Transitions. Resolves the issue where the light/dark mode state resets or fails to initialize after navigating between pages in Astro using the ClientRouter(View Transitions).
---
import { ModeToggle } from "./mode-toggle";
---
<header>
<nav>
<!-- <Menu /> -->
<div class="flex justify-between">
<ModeToggle client:load />
</div>
<Navigation />
</nav>
</header>
<script is:inline>
function setTheme() {
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");
console.log("theme set to", isDark ? "dark" : "light");
// don't use it
// 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"],
// });
// }
}
document.addEventListener("astro:after-swap", () => {
setTheme();
console.log("page loaded");
});
setTheme();
</script>
import { Moon, Sun } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useEffect, useState } from "react";
export function ModeToggle() {
const [theme, setThemeState] = useState<"theme-light" | "dark" | "system">(
() => {
if (typeof window !== "undefined") {
const isDark = document.documentElement.classList.contains("dark");
return isDark ? "dark" : "theme-light";
}
return "theme-light";
}
);
useEffect(() => {
console.log("theme: ", theme);
}, []);
const setTheme = (newTheme: "theme-light" | "dark" | "system") => {
setThemeState(newTheme);
// update localStorage
const themeValue = newTheme === "theme-light" ? "light" : newTheme;
localStorage.setItem("theme", themeValue);
// update DOM
const isDark =
newTheme === "dark" ||
(newTheme === "system" &&
window.matchMedia("(prefers-color-scheme: dark)").matches);
document.documentElement.classList[isDark ? "add" : "remove"]("dark");
};
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("theme-light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
@fairjm
Copy link
Author

fairjm commented Dec 25, 2025

Original code: https://ui.shadcn.com/docs/dark-mode/astro
But it doesn't work with ClientRouter (View Transitions)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment