One of the most requested features for web applications is the ability to switch between light and dark mode. This is a feature that is easy to implement with Tailwind CSS and Astro View Transitions. In this article, we will go over how to set up an interactive dark mode crossing the small problems that you may encounter.
We need to have installed Tailwind in our project, if you don’t have it installed yet, you can do it by running the following command:
astro add tailwind
When you run this command ensure that you have astro installed globally or you can run it with
npx astro add tailwind
.
With this astro automatically adds the necessary configuration to the project. Now we can start using Tailwind classes in our Astro components.
Astro allow us to add inline
scripts to our components, with this we can add script it won’t process or optimize the script, it will be included as is. We can use this to add the script that will handle the dark mode toggle. In this script we will get the current theme from the user and set it in the localStorage
to persist the user’s preference.
The following code get the user preference from the localStorage
if it exists, if not it will check the user’s system preference and set the theme accordingly. Then it will add the dark
class to the html
element if the theme is dark, and remove it if the theme is light. Finally, it will add an event listener to the toggle button to handle the theme change. This button can be any element that you want to use to handle the theme change.
const theme = (() => {
if (typeof localStorage !== "undefined" && localStorage.getItem("theme")) {
return localStorage.getItem("theme");
}
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
return "dark";
}
return "light";
})();
if (theme === "light") {
document.documentElement.classList.remove("dark");
} else {
document.documentElement.classList.add("dark");
}
window.localStorage.setItem("theme", theme);
const handleToggleClick = () => {
const element = document.documentElement;
element.classList.toggle("dark");
const isDark = element.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
};
document
.getElementById("themeToggle")
.addEventListener("click", handleToggleClick);
The theme
variable is an IIFE, it’s executed immediately and returns the user’s preference saved in the localStorage
or if it’s the first time if will get the user’s system preference. Then we make a validation using the variable we got and add or remove the dark
class from the html
element. Finally, we create a function to handle the theme change and add an event listener to the toggle button we have in our component.
With this we can change the theme and save it in the localStorage
to persist the user’s preference. But if you try change the theme you will see it’s not changing, this is because by default Tailwind doesn’t have a dark
class as a selector, so we need to add it to the tailwind.config.mjs
file.
export default {
// ... other configurations***
darkMode: "selector",
};
So now we can use the dark
class to style our components when the theme is dark.
After all theses steps you may think that everything is working fine, but if you go to another page with the view transitions enabled, you will see the theme is not being applied. This is because the dark
class is not being added to the html
element when the page is loaded. For get this working we need to add an event listener to the astro:after-swap
event and set the dark mode again.
document.addEventListener("astro:after-swap", () => {
if (localStorage.getItem("theme") === "dark")
document.documentElement.classList.toggle("dark", true);
});
You may think we got it, because after we go to another page the theme is being applied, but if you try to change the theme with the button you made, the button won’t work itself, this is because the event listener we added to the button is being removed when the page is loaded. We need to add the event listener again after the page is loaded. So our final script will look like this:
document.addEventListener("astro:after-swap", () => {
if (localStorage.getItem("theme") === "dark")
document.documentElement.classList.toggle("dark", true);
if (!document.getElementById("themeToggle").hasAttribute("onclick"))
document
.getElementById("themeToggle")
.addEventListener("click", handleToggleClick);
});
With this we have our interactive dark mode working with Tailwind CSS and Astro View Transitions.