How to Create a Dark Mode Toggle with Tailwind CSS

by Didin J. on Aug 01, 2025 How to Create a Dark Mode Toggle with Tailwind CSS

Learn how to implement a dark mode toggle in your Tailwind CSS project using utility classes and simple JavaScript—fully responsive and easy to set up.

Dark mode has become a popular design trend and a user preference feature that enhances visual ergonomics by reducing eye strain in low-light environments. With Tailwind CSS, implementing a dark mode toggle is both simple and flexible—allowing you to create responsive themes without writing custom CSS.

In this tutorial, you’ll learn how to add a dark mode toggle to your website or app using Tailwind CSS. We’ll walk through setting up the Tailwind configuration, creating the toggle switch with HTML and JavaScript, and applying dark mode utility classes effectively. By the end, you'll have a fully functional theme toggle that enhances user experience and modernizes your UI.


1. Setting Up Tailwind CSS 4.1 with Dark Mode

Tailwind CSS 4.1 continues to support the class strategy for dark mode, but with more streamlined configuration and default optimizations. Below is the modern setup using the recommended Play CDN for fast prototyping or a PostCSS/Vite setup for production.

 

Option 1: Quick Setup Using Tailwind Play CDN

This is great for tutorials, prototyping, or demos.

<!DOCTYPE html>
<html lang="en" class="dark">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Tailwind Dark Mode</title>
  <script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script>
  <script>
    tailwind.config = {
      darkMode: 'class',
    };
  </script>
</head>
<body class="bg-white text-black dark:bg-gray-900 dark:text-white transition-colors duration-300">
  <!-- Your content goes here -->
</body>
</html>

✅ Note: ?plugins=forms,typography,aspect-ratio is optional and can be customized.
✅ Use class="dark" on <html> for manual toggling.

🔧 Option 2: Full Tailwind 4.1 Setup (Recommended for Projects)

If you're building a real project, use this setup with Vite or PostCSS.

Step 1: Install Tailwind CSS

npm install -D tailwindcss@latest postcss@latest autoprefixer@latest

2. Manually create config files

Since the CLI no longer auto-generates them, create these two files manually:

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx,html}'],
  darkMode: 'class',
  theme: {
    extend: {},
  },
  plugins: [],
};

postcss.config.js

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};

3. Create your CSS input file

Create src/style.css and add:

@tailwind base;
@tailwind components;
@tailwind utilities;

4. Build your CSS with Tailwind CLI

Use this command to build CSS and watch for changes:

npx tailwindcss -i ./src/style.css -o ./dist/output.css --watch

Make sure your index.html links the generated CSS:

<link rel="stylesheet" href="/dist/output.css">


2. Creating the Toggle Switch UI

Now that Tailwind CSS 4.1 is set up with darkMode: 'class', we’ll build a dark mode toggle button that updates the <html> class and persists the user’s preference using localStorage.

We'll start with a clean and minimal switch-style toggle. Later, you can enhance it with icons or animations if desired.

<div class="flex items-center justify-center min-h-screen bg-white text-black dark:bg-gray-900 dark:text-white transition-colors duration-300">
  <button id="theme-toggle"
    class="relative inline-flex items-center h-8 w-16 rounded-full bg-gray-300 dark:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors duration-300">
    <span class="sr-only">Toggle dark mode</span>
    <span class="inline-block w-6 h-6 transform bg-white dark:bg-gray-200 rounded-full transition-transform duration-300 translate-x-1 dark:translate-x-8"></span>
  </button>
</div>

Toggle Script with Persistence

Add this right before the closing </body> tag:

<script>
  const themeToggle = document.getElementById('theme-toggle');
  const root = document.documentElement;

  // Load saved theme on initial load
  if (localStorage.theme === 'dark') {
    root.classList.add('dark');
  } else if (localStorage.theme === 'light') {
    root.classList.remove('dark');
  } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
    root.classList.add('dark');
  }

  // Toggle dark mode
  themeToggle.addEventListener('click', () => {
    const isDark = root.classList.toggle('dark');
    localStorage.theme = isDark ? 'dark' : 'light';
  });
</script>

📝 How It Works

  • The toggle button is styled using Tailwind utilities.

  • The inner span moves right or left depending on dark mode (translate-x-1translate-x-8).

  • The theme is saved in localStorage and respected on reload.

  • If no preference is saved, it respects the OS-level preference using prefers-color-scheme.


3. Enhancing the Toggle with Sun and Moon Icons (Heroicons)

This version swaps between a sun (☀️) and moon (🌙) icon depending on the current theme and adds polish to your toggle UI.

Step 1: Include Heroicons

You can include the Heroicons outline set via CDN or inline SVGs. For simplicity, we’ll use inline SVGs here (fully customizable).

Step 2: Updated Toggle Button with Icons

<div class="flex items-center justify-center min-h-screen bg-white text-black dark:bg-gray-900 dark:text-white transition-colors duration-300">
  <button id="theme-toggle" aria-label="Toggle Dark Mode"
    class="flex items-center justify-center w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:ring-2 hover:ring-blue-500 transition">
    <!-- Sun Icon (Light Mode) -->
    <svg id="icon-sun" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 block dark:hidden" fill="none" viewBox="0 0 24 24"
      stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
        d="M12 3v1m0 16v1m8.66-8.66l-.71.71M4.05 4.05l-.71.71M21 12h1M2 12H1m16.95 4.95l.71.71M4.05 19.95l.71-.71M12 8a4 4 0 100 8 4 4 0 000-8z" />
    </svg>

    <!-- Moon Icon (Dark Mode) -->
    <svg id="icon-moon" xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 hidden dark:block" fill="none"
      viewBox="0 0 24 24" stroke="currentColor">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
        d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" />
    </svg>
  </button>
</div>

Step 3: JavaScript (Dark Mode Toggle + Icon Handling)

<script>
  const root = document.documentElement;
  const toggleBtn = document.getElementById('theme-toggle');

  // Apply saved or system preference
  if (localStorage.theme === 'dark' || (!localStorage.theme && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
    root.classList.add('dark');
  } else {
    root.classList.remove('dark');
  }

  // Toggle dark class and persist
  toggleBtn.addEventListener('click', () => {
    const isDark = root.classList.toggle('dark');
    localStorage.theme = isDark ? 'dark' : 'light';
  });
</script>

✅ No need to manually toggle icon visibility — Tailwind’s dark:block and dark:hidden take care of that!

🧪 Test It

  • Click the button: icon and theme toggle smoothly.

  • Refresh the page: your preference is preserved via localStorage.

  • Try switching your OS theme: it respects system settings on first load (if no saved theme).


4. Styling Tips and Accessibility Improvements

Now that your dark mode toggle is functional and looks polished with icons, let’s make sure it's accessible, responsive, and visually refined for a better user experience.

Accessibility Best Practices

  1. Use aria-label to describe the button’s purpose (already added).

  2. Add focus-visible outline so keyboard users can see focus state.

Update your button class like this:

<button id="theme-toggle"
  aria-label="Toggle Dark Mode"
  class="flex items-center justify-center w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 
         hover:ring-2 hover:ring-blue-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 transition">
  1. Add a screen reader label (if needed):
<span class="sr-only">Toggle dark mode</span>

Already covered inside the button.

🎨 Styling Improvements

  • Add transition utilities to the <body> or <html> for smooth background/text color changes:

<body class="transition-colors duration-300">
  • For additional polish, consider adding Tailwind group utilities if using more advanced icon swaps or tooltip components.

📱 Mobile Responsiveness

Your current toggle button is already responsive. However, if you're placing it in a navbar or corner, wrap it in a container:

<div class="fixed top-4 right-4 z-50">
  <!-- toggle button here -->
</div>

Or within a navigation layout:

<header class="flex justify-between items-center px-4 py-2">
  <h1 class="text-xl font-bold">My App</h1>
  <!-- toggle button -->
</header>

🌐 Optional: Set data-theme or theme attribute (for frameworks)

If you're using this toggle in frameworks like React, Vue, or Alpine.js, consider toggling a data-theme="dark" attribute instead of class="dark" for more theme flexibility.

✅ Final Checklist

Feature Done ✅
Tailwind CSS 4.1 installed
Dark mode configured (class)
Toggle button with icons
Theme persisted via localStorage
Accessible + responsive


Conclusion

Implementing a dark mode toggle with Tailwind CSS 4.1 is both simple and powerful. By leveraging Tailwind’s darkMode: 'class' strategy and its utility-first approach, you can easily build a modern, accessible, and visually appealing toggle that enhances user experience and respects their preferences.

In this tutorial, you learned how to:

  • Set up a Tailwind CSS 4.1 project with dark mode support

  • Create a responsive toggle switch with smooth transitions

  • Use localStorage to persist user theme choices

  • Enhance the UI with Heroicons for a polished look

  • Ensure accessibility and responsiveness across devices

With this foundation, you can now expand the toggle feature into larger applications, integrate it into navigation bars, or even switch full-color themes dynamically. Tailwind CSS makes it easy to scale this functionality with consistent design and developer productivity.

You can find the full source code on our GitHub.

That's just the basics. If you need more deep learning about HTML, CSS, JavaScript, or related, you can take the following cheap course:

Thanks!