Implementing Dark Mode in React with Tailwind CSS and Next.js
Dark mode has become a popular feature in modern web applications, offering users a comfortable viewing experience in low-light environments. In this post, we'll walk through implementing a dark mode toggle in a Next.js application using React hooks and Tailwind CSS.
Setting Up the Project
First, make sure you have a Next.js project set up with Tailwind CSS. If you don't, you can create one using the following commands:
npx create-next-app@latest my-dark-mode-app
cd my-dark-mode-app
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
Configuring Tailwind CSS for Dark Mode
Open your tailwind.config.js
file and add the darkMode
option:
module.exports = {
darkMode: 'class',
// ... other configurations
}
This tells Tailwind to use the class
strategy for dark mode, which we'll toggle with JavaScript.
Creating a Dark Mode Hook
Create a new file called useDarkMode.js
in your hooks
directory:
import { useState, useEffect } from 'react';
function useDarkMode() {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
setIsDarkMode(darkModeMediaQuery.matches);
const handleChange = (e) => setIsDarkMode(e.matches);
darkModeMediaQuery.addEventListener('change', handleChange);
return () => darkModeMediaQuery.removeEventListener('change', handleChange);
}, []);
useEffect(() => {
if (isDarkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [isDarkMode]);
const toggleDarkMode = () => setIsDarkMode(!isDarkMode);
return [isDarkMode, toggleDarkMode];
}
export default useDarkMode;
This hook does three things:
- It checks the user's system preferences for dark mode.
- It adds or removes the
dark
class from the<html>
element based on the current mode. - It provides a function to toggle between light and dark modes.
Implementing the Dark Mode Toggle
Now, let's create a component to toggle dark mode. Create a new file called DarkModeToggle.js
in your components
directory:
import React from 'react';
import useDarkMode from '../hooks/useDarkMode';
const DarkModeToggle = () => {
const [isDarkMode, toggleDarkMode] = useDarkMode();
return (
<button
onClick={toggleDarkMode}
className="p-2 rounded-md hover:ring-2 hover:ring-gray-300 dark:hover:ring-gray-700"
>
{isDarkMode ? '🌙' : '☀️'}
</button>
);
};
export default DarkModeToggle;
Using the Dark Mode Toggle
You can now use this toggle in your layout or any component. For example, in your pages/_app.js
:
import '../styles/globals.css';
import DarkModeToggle from '../components/DarkModeToggle';
function MyApp({ Component, pageProps }) {
return (
<div className="min-h-screen bg-white dark:bg-gray-900 transition-colors duration-300">
<nav className="p-4">
<DarkModeToggle />
</nav>
<Component {...pageProps} />
</div>
);
}
export default MyApp;
Styling for Dark Mode
With Tailwind CSS, you can easily add dark mode variants to your styles. For example:
<h1 className="text-gray-900 dark:text-white">
Welcome to my app!
</h1>
<p className="text-gray-600 dark:text-gray-300">
This text adapts to dark mode.
</p>
Persisting User Preference
To remember the user's preference, you can store it in localStorage. Update your useDarkMode
hook:
import { useState, useEffect } from 'react';
function useDarkMode() {
const [isDarkMode, setIsDarkMode] = useState(() => {
if (typeof window !== 'undefined') {
const savedMode = localStorage.getItem('darkMode');
return savedMode ? JSON.parse(savedMode) : false;
}
return false;
});
useEffect(() => {
localStorage.setItem('darkMode', JSON.stringify(isDarkMode));
if (isDarkMode) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [isDarkMode]);
const toggleDarkMode = () => setIsDarkMode(!isDarkMode);
return [isDarkMode, toggleDarkMode];
}
export default useDarkMode;
This implementation checks localStorage for a saved preference and uses it if available. It also updates localStorage whenever the mode changes.
Conclusion
You now have a fully functional dark mode in your Next.js application! The toggle respects user preferences, persists across sessions, and smoothly transitions between light and dark themes. Remember to test your application thoroughly in both modes to ensure all components look great regardless of the chosen theme.
Happy coding, and may your users' eyes thank you for the dark mode option! 🌙✨
If you found this helpful, feel free to follow me on GitHub for more React and Next.js tips.