DEV Community

Toufic Nabi
Toufic Nabi

Posted on • Updated on

How to add dark theme easily in NextJS using Zustand and Tailwind.

Ability to switching theme in your website gives your user a great user-experience. All modern websites nowadays have this feature where you can switch theme between dark and light with a click of a button. Also, the theme preference needs to be store so when the user comes back to the website they have their selected option right away.

Today we will implement theme switching to a Nextjs website using State Management Tool called Zustand and Tailwind. (We could do it using context API but Zustand will be useful in other part of this project, so I used Zustand).

Step 1:
Assuming you have installed and added tailwind in your project. If not, follow these instructions.

Install Zustand: Go into the terminal in vscode in your project folder, run the following command:

npm i zustand
Enter fullscreen mode Exit fullscreen mode

Then create a folder in src folder if your project called store. Create a file called inside of the store folder. (These are the naming and folder structure convention)

Step 2:
Access the html tag
We have to use the html tag for switching theme using a data attribute called data-mode. To access the html tag in the nextjs project we have to use _document.js component provide by the nextjs team. This component goes inside of the pages folder for page based route system or app folder for App based route system.

Step 3:
Add the default theme in html attribute (data-mode) like this:

<Html lang='en-us' data-mode="light">
    <Main />
    <NextScript />
Enter fullscreen mode Exit fullscreen mode

Step 4: Edit the tailwind config
Let's edit how the dark theme works in Tailwind. Inside of tailwind.config.js file, add the darkMode key value pair like follows:

module.exports = {
  darkMode: ['class', '[data-mode="dark"]'],
  // other stuffs
Enter fullscreen mode Exit fullscreen mode

Step 5: Theme switch button
Let's make a button that can switch our theme. Usually, the button goes into the header. Make a button you like. This can be a toggle button or a regular button. We will add the onClick event in step 7.

Step 6: Create the store
When using Zustand, the state management file is called a store. Head over to the file and add the theme element like follows:

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useStore = create(persist(
    (set, get) => ({
        theme: "dark",
        setTheme: () => set((state) => ({
            theme: get().theme === "dark" ? "light" : "dark"
    }), {
        name: 'theme', // name of the item in the storage (must be unique)

export const useTheme = () => useStore((state) => state.theme);
export const useSetTheme = () => useStore((state) => state.setTheme);

Enter fullscreen mode Exit fullscreen mode

This will create, switch and store the theme in the localStorage of the browser.

Now we have access to the theme state and setTheme function in all the components throughout the project.

Step 7:
Import the theme, and setTheme items from the store to the header where your theme switch button is located like follows:

import {useTheme, useSetTheme} from '../store/';
const Header = () => {
  //get the values
  const theme = useTheme();
  const setTheme = useSetTheme();

  return (
    //your stuffs.
Enter fullscreen mode Exit fullscreen mode

then set the buttons onClick to the setTheme function. This action will switch the theme between dark and light, and store the value in localStorage.

Step 8:
Now head over to the _app.js file, inside of useEffect hook, grab the localStorage value for the theme like this:

const theme = useTheme();

useEffect(() => {
  try {
    const localTheme = JSON.parse(localStorage.getItem('theme'));
    if (localTheme) {
      document.documentElement.setAttribute('data-mode', localTheme.state.theme);
      document.documentElement.className = localTheme.state.theme;
  } catch (err) {
    console.log('error loading the color theme')
}, [theme])

Enter fullscreen mode Exit fullscreen mode

Of course, you have to import the theme from the store for this as well.
We are using the theme as dependency so when our theme switch button updates the theme our value in html tag also changes from the localStorage.

Step 9: add classNames for dark theme
Now, go to any of your components or pages and add the class name for dark theme like follows:

<div className="text-black bg:white dark:text-white dark:bg-black">
Enter fullscreen mode Exit fullscreen mode

You should see the theme switching and retaining the preference upon reloading the page.


Top comments (4)

bhendi profile image

What is the advantage of using zustand over normal react state in this scenario ?

touficnabi profile image
Toufic Nabi

The biggest advantage is, state managements are global, meaning if you have many component/page in your project you can access the states from any components easily. On the other hand if you use normal useState hook, you need drill your prop. Also using zustand gives you the easy ability to keep the user preference in localStorage.
Hope this helps

bhendi profile image

But if we create our state inside a custom hook then we don't need prop drilling right ?

Thread Thread
touficnabi profile image
Toufic Nabi

Correct! thats another way of doing it.
But in my opinion, zustand is much more easy to deal with also, easy to manage the state in the localStorage.