Blog Home

Adding a Dark Mode Toggle to Your Astro Website with Tailwind CSS

Let's be honest—what kind of developer are you if you haven't added dark mode to your personal projects? It's practically mandatory at this point. If your site isn't ready to embrace the darkness, don't worry. Today, we're fixing that.

Dark mode isn't just cool, it's a necessity. It saves your eyes and looks slick. In this guide, we'll add a dark mode toggle to your Astro site using Tailwind CSS, making sure it respects system preferences and remembers user choices.

Step 1: Configure Tailwind for Dark Mode

First things first—let's make sure Tailwind is set up to handle dark mode properly. If you're using Tailwind V4, you will probably want to add this custom variant to your Tailwind CSS configuration if you're not using a tailwind.config.js file:

global.css
@custom-variant dark (&:where(.dark, .dark *));

If you are still using an older version of Tailwind prior to V4, you can add the following to your tailwind.config.js file:

tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'selector',
  // ...
}

This ensures that dark mode styles apply when the .dark class is present on the <html> element.

Step 2: Ensure Dark Mode is Applied on Load

No one wants to get flashbanged by a white screen when they clearly chose dark mode. To fix that, add this script inside your <head>:

Layout.astro
<script is:inline>
  // Ensures dark mode is applied before styles load
  if (
    localStorage.theme === 'dark' ||
    (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
  ) {
    document.documentElement.classList.add('dark');
  } else {
    document.documentElement.classList.remove('dark');
  }
</script>

This script runs immediately to prevent a flash of unstyled light mode. The is:inline attribute ensures that Astro inlines the script into the HTML, allowing it to execute as early as possible.

If you're using Astro's page transitions (now ClientRouter), you'll also want to reapply dark mode after navigation swaps:

<script is:inline>
  document.addEventListener('astro:after-swap', () => {
    if (
      localStorage.theme === 'dark' ||
      (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
    ) {
      document.documentElement.classList.add('dark');
    }
  });
</script>

If you're not using page transitions, you can skip this part.

Step 3: Create the Dark Mode Toggle Component

Here's a simple React component to handle the actual toggle component:

DarkModeToggle.jsx
import { useEffect, useState } from 'react';

const DarkModeToggle = () => {
  const [darkMode, setDarkMode] = useState(false);

  useEffect(() => {
    setDarkMode(document.documentElement.classList.contains('dark'));
  }, []);

  const toggleDarkMode = () => {
    document.body.style.transition = 'color 0.1s, background-color 0.3s';
    
    if (darkMode) {
      document.documentElement.classList.remove('dark');
      localStorage.setItem('theme', 'light');
    } else {
      document.documentElement.classList.add('dark');
      localStorage.setItem('theme', 'dark');
    }
    setDarkMode(!darkMode);
  };

  return (
    <button onClick={toggleDarkMode} aria-label="Toggle dark mode" className="p-2">
      {darkMode ? '🌙' : '☀️'}
    </button>
  );
};

export default DarkModeToggle;

Minimal, effective, and most importantly—dark mode enabled.

Step 4: Add the Toggle to Your Navbar

You could just dump this anywhere, but let's be civil and place it neatly in the navbar:

Nav.astro
--- 
import DarkModeToggle from '../components/DarkModeToggle';
---
<nav class="p-4 flex justify-between">
  <h1>My Website</h1>
  <DarkModeToggle client:only="react" />
</nav>

Since DarkModeToggle is a React component, we use client:only="react" to tell Astro to hydrate it only on the client while keeping the rest of the navbar static - you can read more about that here . Now your users can seamlessly switch between dark and light mode.

Step 5: Add the Tailwind class where necessary

Lastly, you just need to add the necessary utility classes to your components. For example on our body, we will add bg-white dark:bg-black. When the dark class is present on the <head>, the bg-black code will be applied to our body. For a better explanation, see the Tailwind docs .

Conclusion

Congratulations! 🎉 Your Astro website is now part of the dark side (or light side, if that's your thing). With Tailwind's .dark class, everything stays simple and controlled, while our React toggle makes switching a breeze.

Your eyes will thank you. 🚀