Building a Dark Mode Toggle with Next.js and Tailwind CSS
Dark mode has become a standard feature for modern web applications. In this post, we'll build a dark mode toggle using Next.js and Tailwind CSS.
Setting Up Tailwind for Dark Mode
First, configure Tailwind CSS to support dark mode in your tailwind.config.js
file:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class", // Use class strategy instead of media queries
theme: {
extend: {},
},
plugins: [],
};
Using "class" for darkMode means we'll toggle classes to switch between themes rather than relying on the user's system preferences (though we can still respect those initially).
Theme Provider with next-themes
The next-themes
package makes theme management in Next.js simple. Let's install it:
npm install next-themes
Now, create a ThemeProvider component:
// src/providers/theme-provider.tsx
"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return (
<NextThemesProvider {...props}>
{children}
</NextThemesProvider>
);
}
Wrap your app with this provider in the root layout:
// src/app/layout.tsx
import { ThemeProvider } from "@/providers/theme-provider";
export default function RootLayout({ children }) {
return (
<html lang="en" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
</ThemeProvider>
</body>
</html>
);
}
Creating the Toggle Component
Now, let's create the actual toggle button:
// src/components/ui/theme-toggle.tsx
"use client";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
export function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
// Avoid hydration mismatch
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="p-2 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
>
{theme === "dark" ? (
<SunIcon className="h-5 w-5" />
) : (
<MoonIcon className="h-5 w-5" />
)}
</button>
);
}
function SunIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
/>
</svg>
);
}
function MoonIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
{...props}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
/>
</svg>
);
}
Using the Toggle
You can now place this toggle anywhere in your application:
import { ThemeToggle } from "@/components/ui/theme-toggle";
export function Navbar() {
return (
<header className="border-b border-gray-200 dark:border-gray-800">
<div className="container flex justify-between items-center">
<div>My Site</div>
<ThemeToggle />
</div>
</header>
);
}
Styling for Dark Mode
With Tailwind, you can easily add dark mode variants to your styles:
<div className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
This text adapts to dark mode!
</div>
Conclusion
Implementing dark mode in Next.js with Tailwind CSS is straightforward thanks to libraries like next-themes. The key components are:
- Configuring Tailwind for dark mode
- Setting up a theme provider
- Creating a toggle component
- Using dark mode variants in your styles
Try implementing this in your Next.js project and watch your users enjoy the option to browse in their preferred theme!