Simple way of how to add a dark mode to your Next.js app using next-themes and TailwindCSS. If you want to add a dark mode to your Next.js project, but don't wanna waste to much time on thinking or setting it up, this aims to show you how you can set it up in 5 mins using next-themes library.
This article already presumes you have a Next.js project set up. If you don't have one, quickly set up a new one by running:
bunx create-next-app@latest my-app-name
Step 1: Install next-themes
next-themes is a widely adopted leightweight library that makes it easy to add a dark mode to your Next.js app. It saves you from the hassle and it works out of the box with different CSS configurations, it can also work with Tailwind.
bun add next-themes
Step 2: Theme Switcher Component
next-themes provides us with a useTheme
hook that we can use to switch between themes. We can create a simple component that will allow us to utilize that functionallity and switch between dark/light theme.
Since hooks can only run on client components, make sure you add "use client";
at the top of the file.
One other thing to note as stated in the next-themes docs as well:
Because we cannot know the theme on the server, many of the values returned from useTheme will be undefined until mounted on the client. This means if you try to render UI based on the current theme before mounting on the client, you will see a hydration mismatch error.
This is why we added the useEffect and mounted
state to make sure the component is mounted and avoid any hydration mismatch errors.
//components/theme-switcher.tsx
"use client";
import { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
const ThemeSwitcher = () => {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
// useEffect only runs on the client, so now we can safely show the UI
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
const handleSetTheme = () => {
if (theme === "dark") {
setTheme("light");
} else {
setTheme("dark");
}
};
return (
<button
type="button"
className="ml-auto dark:hover:bg-gray-600 hover:bg-slate-200 p-2 rounded"
onClick={handleSetTheme}
>
{theme === "dark" ? <SunIcon /> : <MoonIcon />}
</button>
);
};
export default ThemeSwitcher;
There's also an option to to avoid using useEffect to get around this issue by lazy loading the component with next/dynamic.
const ThemeSwitch = dynamic(() => import("@/components/theme-switcher"), {
ssr: false,
});
You can now import this component wherever you want to use it in your UI.
Step 3: Add the ThemeProvider
ThemeSwitcher component will not work without the ThemeProvider.
You can add the ThemeProvider to your layout.tsx
file to make sure it's available throughout your app.
To make it work with TailwindCSS, you can set the attribute
prop to class
and then you can control your UI components styles with dark:
selector.
For example className="dark:background-black"
will apply the background-black
class only for the dark mode.
//app/layout.tsx
import { ThemeProvider } from "next-themes";
....
<ThemeProvider enableSystem={false} defaultTheme="dark" attribute="class">
{children}
</ThemeProvider>;
....
That's all you need to do to add a dark mode to your Next.js app. You can run the app and try clicking the theme switcher button to see the themes switching.