Building a a multi-theme toggle with Astro and Tailwind CSS v4
Create a sleek theme switcher with default, dark, and accent modes using CSS custom properties and localStorage
Published on July 9, 2025 by Michael AndreuzzaFor many web developers, dark mode has become essential for modern web applications, but what if you want to go beyond the basic light/dark toggle? In this tutorial, ya’ll build a sleek a multi-theme switcher that includes default, dark, and accent themes using Astro and Tailwind CSS v4.
Why use a multi-theme toggle?
While most websites take care just light and dark modes ,( Is not like we need more, to be honest…) but adding a third “accent” theme can significantly improve user experience. Or not…depends who visits your site, for eexample, I am just a light mode user…I do not see nothing on a dark mode website only in day time, it’s unreadablde nureadable dark mode only for coding…
This approach gives users more personalization options and can help your brand stand out with a signature color scheme.
Setting up the CSS with Tailwind v4 custom properties
First, let’s define our theme variables inside the @theme.In this case ya’ll use OKLCH color values:
@theme {
/* Styles text */
--color-primary: oklch(0.07 0 0);
/* Styles background */
--color-secondary: oklch(1 0 0);
/* For accents */
--color-accent: oklch(0.67 0.2941 338.67);
}
[data-theme="dark"] {
/* Styles text */
--color-primary: oklch(1 0 0);
/* Styles background */
--color-secondary: oklch(0.37 0.0073 264.48);
/* For accents */
--color-accent: oklch(0.67 0.2941 338.67);
}
[data-theme="accent"] {
/* Styles text */
--color-primary: oklch(1 0 0);
/* Styles background */
--color-secondary: oklch(0.41 0.1992 291.76);
/* For accents */
--color-accent: oklch(0.67 0.2941 338.67);
}
This CSS creates three distinct themes:
- Default: Light theme with dark text on white background
- Dark: Classic dark theme with light text on dark background
- Accent: Purple-tinted theme that offers a unique branded experience
Creating the theme toggle UI
The toggle interface uses three circular buttons, each representing one theme option:
<div class="flex items-center gap-2">
<!-- Default theme: white base -->
<button
data-theme="default"
class="theme-dot outline-primary/10 size-4 rounded-full bg-white outline-2 transition-all"
title="Default">
</button>
<button
data-theme="dark"
class="theme-dot outline-primary/10 size-4 rounded-full bg-black outline-2 transition-all"
title="Dark">
</button>
<button
data-theme="accent"
class="theme-dot outline-primary/10 size-4 rounded-full bg-[#4d22a3] outline-2 transition-all"
title="Accent">
</button>
</div>
The Toggles:
Each button uses Tailwind’s utility classes for styling and includes a data-theme attribute that corresponds to our CSS theme names.
Adding the JS part
Here’s the JavaScript that handles theme switching and persistence:
const dots = document.querySelectorAll(".theme-dot");
const savedTheme = localStorage.getItem("theme") || "default";
function applyTheme(theme) {
if (theme === "default") {
document.documentElement.removeAttribute("data-theme");
localStorage.removeItem("theme");
} else {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}
// Toggle ring highlight
dots.forEach((dot) => {
const isActive = dot.dataset.theme === theme;
dot.classList.toggle("ring-offset-2", isActive);
dot.classList.toggle("ring-current", isActive);
});
}
// Set theme on load
applyTheme(savedTheme);
// Add click listeners
dots.forEach((dot) => {
dot.addEventListener("click", () => {
applyTheme(dot.dataset.theme);
});
});
This script handles theme application, visual feedback for the active theme, and localStorage persistence.
Preventing flash of unstyled content (FOUC)
To avoid the flash of unstyled content when the page loads, add this script in your document head:
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.setAttribute('data-theme', savedTheme);
}
Conclusion
This multi-theme toggle provides users with more customization options while maintaining clean, maintainable code. The combination of CSS custom properties, Tailwind v4 utilities, and vanilla JS creates a robust theming system that works perfectly with Astro’s.
The OKLCH color space ensures consistent colors across different devices, while localStorage persistence means that the users theme preferences are remembered between sessions.
/Michael Andreuzza







