The Easiest Way to Add Dark Mode in React using TailwindCSS


Introduction: From Blinding Lights to Soothing Shadows
Picture this: It's 2 AM, you're deep in a coding session, and suddenly you open a website that feels like staring directly into the sun. Your eyes water, you squint, and you immediately start hunting for that blessed dark mode toggle. Sound familiar?
Dark mode is now an essential part of modern web design, reducing eye strain and saving battery life. Implementing it used to be complex, but with TailwindCSS and React.js, it's straightforward. This article shows you how to add a fully functional dark mode to your React app in just 5 minutes. Let's dive in!
Quick Setup: Prerequisites and TailwindCSS Config
Before we begin, ensure you have a basic React.js project and TailwindCSS installed. If not, set them up:
# Using Create React App
npx create-react-app my-dark-mode-app
cd my-dark-mode-app
# Or using Vite
npm create vite@latest my-dark-mode-app --template react
cd my-dark-mode-app
npm install
# Install tailwindcss
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Update your tailwind.config.js
to enable class-based dark mode. This allows user control over the theme, unlike the media
strategy which relies on system preferences.
/** @type {import(\'tailwindcss\').Config} */
module.exports = {
content: [
"./src/**/*.{js,jsx,ts,tsx}",
],
darkMode: 'class', // Essential for user-controlled dark mode
theme: {
extend: {},
},
plugins: [],
}
Finally, add TailwindCSS directives to your main CSS file (e.g., src/index.css
):
@tailwind base;
@tailwind components;
@tailwind utilities;
Building the Theme Switcher Component
Create src/components/ThemeSwitcher.jsx
. This component manages theme state, persists user preference to localStorage
, and toggles the dark
class on document.documentElement
.
import React, { useState, useEffect } from 'react';
const THEME_KEY = 'theme';
const DARK_CLASS = 'dark';
const LIGHT_CLASS = 'light';
const ThemeSwitcher = () => {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const root = document.documentElement;
const savedTheme = localStorage.getItem(THEME_KEY);
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const shouldUseDark = savedTheme === DARK_CLASS || (!savedTheme && prefersDark);
root.classList.toggle(DARK_CLASS, shouldUseDark);
setIsDarkMode(shouldUseDark);
}, []);
const toggleTheme = () => {
const nextTheme = isDarkMode ? LIGHT_CLASS : DARK_CLASS;
document.documentElement.classList.toggle(DARK_CLASS, nextTheme === DARK_CLASS);
localStorage.setItem(THEME_KEY, nextTheme);
setIsDarkMode(nextTheme === DARK_CLASS);
};
return (
<button
onClick={toggleTheme}
className="p-2 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 transition-colors duration-200 hover:bg-gray-300 dark:hover:bg-gray-600"
aria-label={isDarkMode ? "Toggle light mode" : "Toggle dark mode"}
>
{isDarkMode ? (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clipRule="evenodd" />
</svg>
) : (
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" />
</svg>
)}
</button>
);
};
export default ThemeSwitcher;
Styling with the dark:
Prefix
TailwindCSS's dark:
prefix applies styles only when the dark
class is present on a parent element. This allows for conditional styling without extra CSS.
Example:
<div className="bg-white text-black dark:bg-gray-900 dark:text-white">
This text adapts to the current theme
</div>
Here's a ThemeAwareCard
example:
const ThemeAwareCard = ({ title, content, author }) => {
return (
<div className="max-w-md mx-auto bg-white dark:bg-gray-800 rounded-xl shadow-lg dark:shadow-gray-900/50 overflow-hidden transition-all duration-300 hover:shadow-xl dark:hover:shadow-gray-900/70">
<div className="bg-gradient-to-r from-blue-500 to-purple-600 dark:from-blue-600 dark:to-purple-700 px-6 py-4">
<h2 className="text-xl font-bold text-white">{title}</h2>
</div>
<div className="px-6 py-4">
<p className="text-gray-700 dark:text-gray-300 leading-relaxed mb-4">
{content}
</p>
<div className="flex items-center">
<div className="w-10 h-10 bg-gray-300 dark:bg-gray-600 rounded-full flex items-center justify-center">
<span className="text-gray-600 dark:text-gray-300 font-semibold">
{author.charAt(0).toUpperCase()}
</span>
</div>
<div className="ml-3">
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
{author}
</p>
<p className="text-xs text-gray-500 dark:text-gray-400">
2 hours ago
</p>
</div>
</div>
</div>
<div className="px-6 py-4 bg-gray-50 dark:bg-gray-700 border-t border-gray-200 dark:border-gray-600">
<div className="flex space-x-3">
<button className="flex-1 bg-blue-500 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200">
Like
</button>
<button className="flex-1 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-800 dark:text-gray-200 font-medium py-2 px-4 rounded-lg transition-colors duration-200">
Share
</button>
</div>
</div>
</div>
);
};
Integrating the Toggle into Your App
Place the ThemeSwitcher
component in your main App
component or a header/navigation component:
// App.js
import React from 'react';
import ThemeSwitcher from './components/ThemeSwitcher';
import ThemeAwareCard from './components/ThemeAwareCard';
function App() {
return (
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 transition-colors duration-300">
<header className="bg-white dark:bg-gray-800 shadow-sm dark:shadow-gray-900/50 border-b border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
My Awesome App
</h1>
<ThemeSwitcher />
</div>
</div>
</header>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
Welcome to Dark Mode Paradise
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<ThemeAwareCard
title="Getting Started"
content="Learn how to implement dark mode in your React applications with TailwindCSS. It\'s easier than you think!"
author="John Doe"
/>
<ThemeAwareCard
title="Advanced Techniques"
content="Discover pro tips and best practices for creating beautiful dark mode experiences that your users will love."
author="Jane Smith"
/>
<ThemeAwareCard
title="Design Principles"
content="Understand the psychology behind dark mode and how to design interfaces that work perfectly in both themes."
author="Mike Johnson"
/>
</div>
</main>
<footer className="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-16">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div className="text-center">
<p className="text-gray-600 dark:text-gray-400">
ยฉ 2024 My Awesome App. Built with React and TailwindCSS.
</p>
}
</div>
</footer>
</div>
);
}
export default App;
Conclusion
You've now implemented a professional dark mode feature. This setup provides intelligent theme detection, persistent user preferences, seamless switching, and comprehensive styling with TailwindCSS. The simplicity of this approach makes it powerful and maintainable for any React application. Happy coding!
Ready to try it yourself? Implement dark mode in your next React project and share your experience in the comments below!
Like and share if you found this useful ๐๐ฝ
Subscribe to my newsletter
Read articles from Cynthia Emerenini directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Cynthia Emerenini
Cynthia Emerenini
Frontend Software Engineer with years of experience in solving software problems/needs.