Implement dark mode in your Astro project with Tailwind CSS

August 16, 2023
3 min read
By Cojocaru David & ChatGPT
index

How to Implement Dark Mode in Astro with Tailwind CSS

Want to add dark mode to your Astro site using Tailwind CSS? This step-by-step guide shows you how to seamlessly integrate a user-friendly dark/light theme toggle with minimal code. We’ll use Astro’s inline scripts and Preact for a smooth, flash-free implementation.

Why Dark Mode Matters

Dark mode reduces eye strain, saves battery life, and improves accessibility. With Tailwind CSS, enabling it is straightforward—just toggle a dark class on your HTML element.

“Dark mode isn’t just a trend—it’s a usability enhancement.”

Setting Up Your Astro Project

Install Dependencies

First, create a new Astro project and add Tailwind CSS + Preact:

npm create astro@latest
npm install -D @astrojs/tailwind @astrojs/preact
npm install preact

Configure Tailwind for Dark Mode

Update tailwind.config.cjs to enable class-based dark mode:

module.exports = {
  content: ["./src/**/*.{js,ts,jsx,tsx,astro}"],
  darkMode: "class",
  theme: {},
  plugins: [],
};

Implementing Theme Detection

Inline Script for Instant Loading

Add this script to your Layout.astro to prevent theme flickering:

<script is:inline>
  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";
  })();
 
  document.documentElement.classList.toggle("dark", theme === "dark");
  localStorage.setItem("theme", theme);
</script>

How it works:

  • Checks for saved user preference in localStorage.
  • Falls back to system preferences.
  • Applies the dark class instantly.

Building the Theme Toggle

Preact Component Example

Create a toggle button with state management:

import { useEffect, useState } from "preact/hooks";
 
export default function ThemeToggle() {
  const [theme, setTheme] = useState("light");
 
  useEffect(() => {
    const savedTheme = localStorage.getItem("theme") || 
      (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
    setTheme(savedTheme);
  }, []);
 
  const toggleTheme = () => {
    const newTheme = theme === "light" ? "dark" : "light";
    setTheme(newTheme);
    document.documentElement.classList.toggle("dark", newTheme === "dark");
    localStorage.setItem("theme", newTheme);
  };
 
  return <button onClick={toggleTheme}>{theme === "light" ? "🌙" : "🌞"}</button>;
}

Handling Server-Side Rendering

Avoiding Hydration Mismatches

For SSG, use a mounted state to delay rendering until client-side APIs are available:

const [isMounted, setIsMounted] = useState(false);
 
useEffect(() => {
  setIsMounted(true);
}, []);
 
if (!isMounted) return null; // Skip SSR render

Key takeaways:

  • Prevents localStorage errors during build.
  • Ensures consistent theming after hydration.

Final Tips for Optimization

  • Use Tailwind’s dark: prefix for dark-mode styles (e.g., dark:bg-gray-900).
  • Test across devices to ensure theme persistence.
  • Consider adding a transition for smoother toggling.

#TailwindCSS #Astro #DarkMode #WebDev