Dark mode in Next-js-13 App using Tailwind CSS: Ultimate Guide.

Learn how to implement Dark mode in the Next-js-13 App using Tailwind CSS-Beginner's Guide. Add dark mode to transform your web design for better user experience.

Hello everyone, I hope you are good and doing well. Today, we are going to discuss how we can add Dark mode in Next-js-13 App using Tailwind CSS-Beginner’s Guide. So, Let’s begin.

Having a bright screen at night is painful for our eyes. That’s why the dark mode feature is necessary if we want to take care of our user’s eyes. 

In this lesson, we’ll learn how to enable Dark mode in Next-js-13 App using Tailwind CSS to keep our users engaged with our app all night long. 

We will see how we can easily implement the dark mode feature by using and combining next-themes and Tailwind CSS. 

And we’ll add the ability for our users to toggle between light and dark modes from the user interface.

Tech Stack used:

  1. Next.js 13 App directory
  2. Tailwind CSS
  3. React.js

Npm Packages used:

  1. next-themes
  2. @heroicons/react

Here is a little demo of the project, that we are going to build and the GitHub link to the project.

Demo Link of the Project : Dark mode in Next-js-13 App using Tailwind CSS

Github Link of the Project

Dark mode in Next-js-13 App using Tailwind CSS
Dark mode in Next-js-13 App using Tailwind CSS

Step-1: Setup a New Next.Js-13 Project

First of all, create a Next.js-13 project and install Tailwind CSS to it. Run the below command to create a new Next.js-13 app.

npx create-next-app@latest nextjs-project
Bash

After executing the above command, please select the choices according to the below-given instructions.


 Would you like to use TypeScript with this project? ... No / Yes
 Would you like to use ESLint with this project? ... No / Yes
 Would you like to use Tailwind CSS with this project? ... No / Yes
 Would you like to use `src/` directory with this project? ... No / Yes
 Would you like to use the experimental `app/` directory with this project? ... No / Yes
 What import alias would you like configured? ... @/*
Bash

I have chosen yes for all of them, as I will be using Typescript and Tailwind CSS with Nextjs in this project.

After that, you need to install two packages next-themes and  @heroicons/react from npm so that we can add the Dark Mode in Nextjs 13 using Tailwind CSS.

npm install next-themes @heroicons/react
Bash

Step 2: Creating Components

First of all, visit the page.tsx file inside the app directory delete everything inside it and paste the following code:

Inside page.tsx (app folder):


export default function Home() {
  return (
    <div className="flex flex-col items-center justify-center space-y-10 mt-28">
      <div className="flex flex-col items-center space-y-6">
        <h1 className=" max-w-3xl text-center font-bold text-gray-900 dark:text- 
          white text-5xl leading-tight">
          Welcome to Next.js Dev,Blogging Platform for Devs.
        </h1>
        <p className="text-lg font-medium  text-gray-900 dark:text-white">
        Add Dark mode in the Next-js-13 App using Tailwind CSS
        </p>
      </div>
      <button className=" text-lg w-[180px] bg-gradient-to-r from-[#ff874f] to- 
        [#ec5e95] rounded-lg text-gray-50 font-semibold py-[10px] px-4">
        Get Started
      </button>
    </div>
  );
}
JavaScript

Also, change the layout.tsx file as well.

Inside layout.tsx file:

import "./globals.css";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";

export const metadata = {
  title: "Next.js 13 Dark Mode with Tailwind CSS",
  description: "Adding Dark mode in Next-js-13 App using Tailwind CSS.",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="min-h-screen mx-auto max-w-6xl flex flex-col bg-white 
          dark:bg-gray-900">
          <Navbar />
          <main className="flex flex-col flex-1 max-w-6xl w-full  ">
            {children}
          </main>
          <Footer />
      </body>
    </html>
  );
};
JavaScript

You might be getting errors about the Navbar and Footer components because we have created them yet.

So let’s create Navbar and Footer components and import them inside the layout.tsx file so that we can test the dark mode and light mode theme functionality in our app. 

So, first of all, create a components folder in the root of the directory.

Inside the components, create two files:

  1. Navbar.tsx
  2. Footer.tsx

Inside Navbar.tsx, please add the below code:/c


import Link from 'next/link'
import React from 'react'

const Navbar = () => {
  return (
    <header className="h-15 w-full  shadow-sm dark:border-gray-700">
    <div className="container  px-4 sm:px-6 py-4 flex justify-between items-center">
      {/* Logo */}
       <h2 className='font-bold text-3xl  text-gray-900 dark:text-white'>
         <Link href="https://nextjsdev.com" >
         Next.js Dev
         </Link>
       </h2>

        {/* Theme Switcher  */}
    </div>
  </header>
  )
}

export default Navbar
JavaScript

Inside the Footer.tsx file:


import React from "react";

const Footer = () => {
  return (
    <footer className="px-4 sm:px-6 py-6 mt-10">
      <div className="text-center text-sm text-gray-500">
        <span className="dark:text-gray-100 text-gray-900 font-bold text-lg mr-2">
          {" "}
          Copyright Next.js Dev -
        </span>{" "}
        © {new Date().getFullYear()} All Rights Reserved
      </div>
    </footer>
  );
};

export default Footer;
JavaScript

So in the Navbar.tsx, you can see that we have simply added a nav container and added a logo inside it. we have left some space for the ThemeSwitcher component that we will create later. 

Similarly, we have created a simple Footer component as well, which contains a simple footer container with some text in it.

Next-Auth Authentication in Next.js 13 App Directory: Beginner’s Tutorial

Step-3 Creating the ThemeProvider and ThemeSwitcher

After, creating, now let’s come to the important part. If you are not aware in Next.js-13, all components that we create the files like Navbar.tsx or Footer.tsx or page.tsx, all of them are Server Components by default.

So, if you try to import the {ThemeProvider} form next-themes and use it inside the layout.tsx, you will get an error, because {ThemeProvider} is a ContextProvider and it will only work inside a Client Component.

This is the main thing to note, that’s why to solve this problem, we need to create two more components inside the components folder.

  1. Provider.tsx (It will be a client component that will help to provide access to the ThemeProvider to our whole application)
  2. ThemeSwitcher (It is also a client component and will help to manually change the theme of the app)

Now let’s create the Provider.tsx and ThemeSwitcher.tsx   inside the components folder

Inside Provider.tsx file:


"use client"

import {ThemeProvider} from 'next-themes';
import { useState, useEffect } from 'react'

type Props = {
    children: string | React.JSX.Element | React.JSX.Element[];
  }

const Provider = ({children} : Props) => {

const [mounted,setMounted] = useState<boolean>(false);

useEffect (() => {
    setMounted(true);
},[]);

if(!mounted){
    return <>{children}</>;
}


  return (
    <ThemeProvider enableSystem={true} attribute='class'>
      {children}
    </ThemeProvider>
  )
}

export default Provider;
JavaScript

Inside ThemeSwitcher.tsx file:


"use client";

import { useTheme } from "next-themes";
import { SunIcon } from '@heroicons/react/24/outline';
import { MoonIcon } from '@heroicons/react/24/solid';


const ThemeSwitcher = () => {
 
  const {systemTheme, theme, setTheme } = useTheme();

  const renderThemeChanger= () => {
 
    const currentTheme = theme === "system" ? systemTheme : theme ;

    if(currentTheme ==="dark"){
      return (
        <SunIcon className="w-6 h-6 text-yellow-500 " role="button" onClick={() => 
          setTheme('light')} />
      )
    }

    else {
      return (
        <MoonIcon className="w-6 h-6 text-gray-900 " role="button" onClick={() => 
          setTheme('dark')} />
      )
    }
 };

  return (
    <>
    {renderThemeChanger()}
    </>
  );
};

export default ThemeSwitcher;
JavaScript

So what’s happening here is that we used the ‘useTheme` hook from `next-themes`, along with the `MoonIcon` and the `SunIcon` components from heroicons.

Then, we retrieve the `systemTheme`, the `theme`, and the `setTheme` properties by calling `useTheme` at the top of your component.

From there, create a new method named `renderThemeChanger` and get the current theme from the system or the `theme` variable.

Now, if the current theme is dark, we return the `SunIcon` component and implement the `onClick` event by using the `setTheme` method from `next-themes` to toggle the theme back to light on click to this icon.

Otherwise, we return the `MoonIcon` component and set the theme to dark on click.

Finally, import the Provider component inside the layout.tsx component to wrap the web app to provide the ThemeProvider

Inside the layout.tsx file:


import Navbar from "@/components/Navbar";
import "./globals.css";
import Footer from "@/components/Footer";
import Provider from "@/components/Provider";

export const metadata = {
  title: "Next.js 13 Dark Mode with Tailwind CSS",
  description: "Adding Dark mode in Next-js-13 App using Tailwind CSS.",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="min-h-screen mx-auto max-w-6xl flex flex-col bg-white 
        dark:bg-gray-900">
        <Provider>
          <Navbar />
          <main className="flex flex-col flex-1 max-w-6xl w-full  ">
            {children}
          </main>
          <Footer />
        </Provider>
      </body>
    </html>
  );
}
JavaScript

Ans also import the ThemeSwitcher component inside the Navbar.tsx file, where we left the space for the the ThemeSwitcher component.

Inside the Navbar.tsx file:

After all that open the tailwind.config.js file and then add a property darkMode in it and give a value “class” to it like this.

Inside tailwind.config.ts file:


/** @type {import('tailwindcss').Config} */

module.exports = {
  darkMode:"class",
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {},
  plugins: [],
}
JavaScript

Now, whenever, the `dark` class is present earlier in the HTML tree, tailwind CSS will apply the dark styles, otherwise, it applies the light theme by default.

After that, open the terminal and start the server, the app will be running on localhost:3000, and you might encounter an error, which will be a Hydration Error.

Step-4 Fixing Hydration Mismatch Problem

You might be wondering why we are getting a hydration mismatch error.

So to summarize in short, we cannot know the theme on the server, the values returned from `useTheme` will be `undefined` until mounted on the client.

In other words, our theme icon will not match the actual current theme. And that’s pretty bad for user experience.

So to fix this, we need to make sure we only render our icon when the page is mounted on the client.

So, import the `useState` and `useEffect` hooks from `react`. And create a new state variable to track if the component has been mounted or not on the client side.

Set the initial value to false, and then set its value to true inside a `useEffect` hook.

Finally, inside the `renderThemeChanger`, check if the component is mounted before rendering the UI for the theme changer.

Inside ThemeSwitcher.tsx file:


"use client";

import { useState, useEffect } from "react";
import { useTheme } from "next-themes";
import { SunIcon } from '@heroicons/react/24/outline';
import { MoonIcon } from '@heroicons/react/24/solid';


const ThemeSwitcher = () => {
  const [mounted, setMounted] = useState(false);
  const {systemTheme, 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 renderThemeChanger= () => {
    if(!mounted) return null;

    const currentTheme = theme === "system" ? systemTheme : theme ;

    if(currentTheme ==="dark"){
      return (
        <SunIcon className="w-6 h-6 text-yellow-500 " role="button" onClick={() => 
         setTheme('light')} />
      )
    }

    else {
      return (
        <MoonIcon className="w-6 h-6 text-gray-900 " role="button" onClick={() => 
         setTheme('dark')} />
      )
    }
 };

  return (
    <>
    {renderThemeChanger()}
    </>
  );
};

export default ThemeSwitcher;
JavaScript

Step-5 Checking the Dark Mode Functionality

Now that everything is all set, try to refresh the browser or restart the server.

You can now change the theme of your application manually from the UI which is what we wanted.

You can easily change those dark variants by just adding classes like this:


<h1 className="dark:bg-gray-900 dark:text-gray-100 " >This is H1 heading</h1>
HTML

Conclusion: Dark mode in Next-js-13 App using Tailwind CSS

So that was all about adding Dark mode in Next-js-13 App using Tailwind CSS. 

I hope you like this and enjoyed building this project.

If you think that this was helpful then please do consider visiting my original blog link, follow me on Twitter, and connect with me on LinkedIn.

If you were stuck somewhere and not able to find the solution you can check out my completed GitHub repository here.

Thanks for the time to read this project, if you like this please share it on Twitter and Facebook or any other social media and tag me there.

Some Useful Link:

  1. Next.js and Tailwind Installation Docs
  2. Github link for the project

Vikas Rai

Vikas Rai

I'm Vikas Rai, a 23-year-old self-taught web developer with a passion for crafting web applications and side projects. My toolbox includes Next.js and Tailwind CSS, two technologies I hold dear for web development. I curate content on my personal blog, Nextjsdev.com, where I share insightful articles about Next.js, React.js, Tailwind CSS, and various projects I've built using these tools.

One comment

Leave a Reply

Your email address will not be published. Required fields are marked *