devGalaktikadevGalaktika

10 Dec, 2024

Dark Mode in Next.js in 5 minutes

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.

You can find the package here

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.

Get your own minimalistic next.js blog boilerplate