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.
✅ Useclass="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-1
→translate-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
anddark: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
-
Use
aria-label
to describe the button’s purpose (already added). -
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">
- 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:
- HTML & HTML5 For Beginners (A Practical Guide)
- Web - HTML
- Learn HTML, CSS, JAVASCRIPT
- JavaScript:
- Learn JavaScript Fundamentals
- Learning JavaScript Shell Scripting
Thanks!