Implement dark mode in your Astro project with Tailwind CSS

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

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

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