How to integrate Dark Mode in Next Js 13 with Tailwind CSS
Why use dark mode?
Dark mode is important in websites because it improves the user experience, reduces eye strain and fatigue, conserves energy and battery life, and enhances the overall design and aesthetic appeal of the website. Dark backgrounds make text and images stand out more, making it easier for users to read and interact with the website, and also creating a more immersive and visually appealing experience. Additionally, dark mode is easier on the eyes and can potentially improve sleep quality, especially in low-light environments. Dark mode has also become a popular design trend, making it important for websites looking to appeal to a younger, tech-savvy audience.
Is implementing Night mode in Next Js different from React?
Since Next Js has the power of SSG (Static Site Generation) & SSR (Server Side Rendering), so if you try to integrate night mode as you did in your previous projects in React or HTML/CSS it will not work properly and will throw errors. The reason is that in React or HTML/CSS, you set the theme modes (like "light", "dark" or "default") in the local storage of your browser and Next Js comes with Server Side Components, the server won't know what is happening inside your browser's local storage so the server sends the components on "default" mode. The default mode can be anything, it depends upon your code. After that when the server-side components/pages check the local storage and change its colors you will get an error "Error: Hydration failed because the initial UI does not match what was rendered on the server"
So to solve this you should not render your layouts and pages as client-side components*, they can throw some different kinds of errors*
The reason is that Page and Layout these two things were made in Next Js to create dynamic metadata, title, and a lot more, and those things are not possible to be generated by the client side.
Let's start by creating a Next Js Web App
Single line command to create a Next Js Project with Tailwind CSS
npx create-next-app@latest my-app
After running this command you'll face some questions
Ok to proceed? (y) Enter
Would you like to use TypeScript with this project? ... No / Yes Your Wish, I chose Yes
Would you like to use ESLint with this project? ... No / Yes Choose Yes
Would you like to use Tailwind CSS with this project? ... No / Yes Choose Yes
Would you like to use
src/
directory with this project? ... No / Yes Choose YesWould you like to use experimental
app/
directory with this project? ... No / Yes √ Choose Yes (not for production, only for learning the new features)What import alias would you like configured? ... @/* Your Wish, I chose No
After that, your Next Js Application will be created within a few seconds, let's move ahead and add some codes
Some Basic Concepts of Next Js 13
Now move to the my-app/src/app
folder, here you will see layout.tsx
and page.tsx
files (layout.jsx
and page.jsx
files if you chose JavaScript).
The layout file helps to maintain a particular layout all over the website, for example, you can add a Header or Navbar inside the layout file then the Header or Navbar will be visible all over the website, no need to add the same component on every single page.
The page file is the web page file of that particular route, for example, /src/app/page.tsx
indicates this URL http://localhost:3000/
and /src/app/manjesh/page.tsx
indicates this URL http://localhost:3000/manjesh/
for a detailed explanation of the Next Js 13 routing system, you can check their documentation here
Let's start coding
Run those commands respectively one by one
cd my-app
npm install next-themes react-icons
This package next-themes will help you a lot to make this night mode feature work very quickly. Add the code given inside the tailwind.config.js
file
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: [
'./src/pages/**/*.{js,ts,jsx,tsx}',
'./src/components/**/*.{js,ts,jsx,tsx}',
'./src/app/**/*.{js,ts,jsx,tsx}',
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
},
},
},
plugins: [],
}
Don't worry if this file is showing any errors, you just run the app it will work smoothly
Create a components folder inside the /src/app
directory, then create a Themeprovider file inside the /src/app/components
directory, remember the theme mode will be stored inside the local storage so it must be a client-side component. Let's see the code
/src/app/components/Themeprovider.tsx
"use client";
import { ThemeProvider } from "next-themes";
import { ReactNode } from "react";
const Providers = ({ children }: { children: ReactNode }) => {
return (
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
);
};
export default Providers;
Create a theme changer component so that a user can choose a theme on his preferences. This component also must be a client-side component since this will be dealing with the local storage of the browser
/src/app/components/Themechanger.tsx
"use client";
import React, { useEffect, useState } from "react";
import { BsMoonStarsFill, BsFillSunFill } from "react-icons/bs";
import { useTheme } from "next-themes";
const Themechanger = () => {
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 light = theme === "light";
return (
<button className="fixed z-40 bottom-5 right-5 dark:bg-gray-900 dark:text-yellow-400 bg-gray-100 text-gray-900 w-10 h-10 rounded-full flex justify-center items-center">
{light ? (
<BsMoonStarsFill onClick={() => setTheme("dark")} size={27} />
) : (
<BsFillSunFill onClick={() => setTheme("light")} size={27} />
)}
</button>
);
};
export default Themechanger;
Here mounted
state is created and useEffect
hook is used together in this code to ensure that the component only renders on the client side after the useEffect
hook has been completed.
The mounted
state is initially set to false
, and then the useEffect
hook is used to update the mounted
state to true
. By default, useEffect
only runs on the client side, so it ensures that the component will only render after the client has received the necessary data from the server side, so this can help you to stay away from errors.
Now let's edit the layout file
/src/app/layout.tsx
import "./globals.css";
import { Metadata } from "next";
import Providers from "./components/Themeprovider";
import Themechanger from "./components/Themechanger";
export const metadata: Metadata = {
title: "Create Night Mode",
description: "Manually Created",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<Providers>
<div className="overflow-x-hidden">
<Themechanger />
{children}
</div>
</Providers>
</body>
</html>
);
}
Now let's add some changes inside the page file then it's done
/src/app/page.tsx
import React from "react"
const page = () => {
return (<div className='text-blue-900 text-3xl font-extrabold dark:text-yellow-300 w-full min-h-screen'>
Hello World!
</div>)
}
export default page
Now run the app
cd my-app
npm run dev
Let's see, woohoo 🥳🥳 it's working
Don't worry about the console warnings you see on the right side, it's due to the Grammarly extension, if you have some extensions like that you'll see those kinds of warning messages but they don't affect your web app. So don't worry and code your project
I hope this article was helpful to you thank you, and don't forget to follow me for more content like this and subscribe to my newsletter, share this article with your friends who are learning web development or Next Js, feel free to ask your doubts and comment your feedback, and have a good day✌️🤗
Subscribe to my newsletter
Read articles from Manjesh Kumar Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Manjesh Kumar Sharma
Manjesh Kumar Sharma
Hi there, it's Manjesh Kumar Sharma a 19 Y/O MERN Stack & Next Js Developer. I'm a Database enthusiast, I really enjoy learning and testing different kinds of Databases like MSSQL, MongoDB, Redis, etc. More than that I'm also interested to learn React Native and I'm looking to contribute to open-source✌️.