Home
The Vimwiki logo.
Sunday, March 10, 2024

Interactive dark mode using Tailwind CSS and Astro View Transitions.

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.

Setting up the toggle button

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.

Real problem with View Transitions

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.

Share on Twitter Twitter logo