Loading....

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

Published on:
Modified on:

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 theme.store.js 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">
  <body>
    <Main />
    <NextScript />
  </body>
</Html>
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 theme.store.js 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) => ({
            ...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/theme.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.
Done!

Enjoy!