Table of Contents
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:
- Next.js 13 App directory
- Tailwind CSS
- React.js
Npm Packages used:
- next-themes
- @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
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
BashAfter 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? ... @/*
BashI 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
BashStep 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>
);
}
JavaScriptAlso, 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>
);
};
JavaScriptYou 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:
- Navbar.tsx
- 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
JavaScriptInside 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;
JavaScriptSo 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.
- Provider.tsx (It will be a client component that will help to provide access to the ThemeProvider to our whole application)
- 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;
JavaScriptInside 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;
JavaScriptSo 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>
);
}
JavaScriptAns 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: [],
}
JavaScriptNow, 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;
JavaScriptStep-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>
HTMLConclusion: 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: