Easiest way to add theme to Next.js app using Tailwind
Tags: nextjs, tailwindcss, themes, ui
Published: 2023-09-07 08:22:46

Have you ever gone through the hassle of props drilling, state management, or useContext hook when implementing a dynamic theme in your project? With Next.js + TailwindCSS in your stack, its ridiculously easy to do it. Look no further! With the magic of **_next-themes_**, you can effortlessly manage your web app's theme without breaking a sweat.

## The Power of next-themes
[next-themes](https://www.npmjs.com/package/next-themes) is a simple, yet incredibly effective library that seamlessly integrates with your Next.js and Tailwind CSS stack. It offers an elegant solution to managing themes in your web application with:

1. No Tedious State Management
2. No need to store theme in localStorage
3. Effortless Theme Switching (in minimum lines of code)

## Get Started

1) Getting started with `next-themes` is a piece of cake:

```bash
npm install next-themes
# or
yarn add next-themes
```

2) Configure the provider in _app.js or _app.ts, inside the _app.js file wrap Component with the ThemeProvider provided by next-themes.

Since we're using Tailwind CSS which uses class for styling, We need to pass attribute="class" for ThemeProvider to tell it we're using class to style the theme.

```jsx
import '@/styles/globals.css'
import { ThemeProvider } from 'next-themes'

export default function App({ Component, pageProps }) {
 
 return( 
    <ThemeProvider attribute="class" >
      <Component {...pageProps} />
    </ThemeProvider>
      )
}
```

3) Configure `tailwind.config.js`. The darkMode: class property, This tells Tailwind CSS that we're changing the theme manually instead of relying on the system preference. No need to change any other property.

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

module.exports = {
  content: [
......................................................
  ],
  // important property to add
  darkMode: "class",    
  theme: {
    extend: {
      backgroundImage: {
...........................................................
      },
    },
  },
  plugins: [],
}
```


4) Create your theme switch button/component which you can place anywhere in the header or footer to change the theme of web app.
**Important Note** : This component should give some kind of hydration error which we will tackle in the last.

```jsx
import React from 'react'
import {SunIcon } from "@heroicons/react/24/outline";
import {BsMoonStars} from "react-icons/bs"
import { useTheme } from 'next-themes';

const ThemeButton = () => {
const { theme, setTheme } = useTheme("dark");

  return (
    <div className="h-9 w-9 flex justify-center rounded-full md:p-0 ml-2 bg-gray-800">
    <button
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
    >
      {theme === "dark" ? (
        <SunIcon className="h-8 w-8 text-orange-400" />
      ) : (
        <BsMoonStars className="h-6 w-6 rounded-full text-gray-100" />
      )}
    </button>
  </div>
  )
}

export default ThemeButton
```

![Thats it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zlkbyz30s9i4vszojb46.gif)
**LITERALLY THATS IT.**
Now you you can just click on the theme button, and see the default color contrast of components changing on its own. This is managed by next-themes. **Isnt this _magical_**, you have not defined any dark mode colors yet. I know it might not look best rn, but its a good start.

5) Now you can change the colors of the dark theme just by adding `dark:` infront of the taliwind className properties. For ex, just look at the className of any of the below html tags in my Header component. **Note**: Remember you dont have to import any hook or theme variable to other componentd as we are not setting any theme there, next-themes take care of it. Just write the dark mode theme style however you want, and it will override the default.

```jsx
  return (
    <div>
      <header className='relative flex-grow top-0 w-full bg-white dark:bg-gray-900'>
        <div className='flex w-full p-3 sm:p-4 mt-1 items-center'>

          <form className='flex flex-grow border border-gray-200 hover:shadow-md focus-within:shadow-lg rounded-full px-4 py-2 ml-5 mr-5 items-center max-w-xs sm:max-w-2xl
           dark:border-gray-600 dark:hover:bg-gray-800 group'>
            <input
              ref={searchInput}
              type='text'
              className='flex-grow w-full focus:outline-none dark:text-white dark:bg-gray-900 dark:group-hover:bg-gray-800'
            />
            <XMarkIcon
              className='h-6 text-gray-500 dark:text-gray-400 cursor-pointer transition duration-100 hover:scale-125'
              onClick={() => searchInput.current.value = ""}
            />
            <img
              src='/microphone.svg'
              className='h-6 ml-3 hidden cursor-pointer sm:inline-flex border-l-2 pl-4 border-gray-200 dark:border-gray-600'
            />
            <MagnifyingGlassIcon
              className='h-6 text-blue-500 dark:text-blue-500 hidden sm:inline-flex ml-2 cursor-pointer transition hover:scale-125'
              onClick={search}
            />
            <button hidden type='submit' onClick={search}>Search</button>
          </form>
        </div>
      </header>
    </div>
  );
}

export default SearchHeader;
```

- You can also create cutsom css classes like this, go to globals.css in your directory and in @layer components create the classes.

```css
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components{
    .link{
        @apply hover:underline cursor-pointer dark:text-gray-300 dark:hover:text-orange-500;
    }
}
```

- And you those css classes in your components

```html
      <div className="flex justify-center space-x-8 whitespace-nowrap md:justify-self-start px-2 py-2 ">
        <a href="https://www.linkedin.com/in/vasu-jhawar/" className="link">
          LinkedIn
        </a>
        <a href="https://github.com/vasujhawar2001" className="link">
          GitHub
        </a>
        <a href="https://twitter.com/JhawarVasu" className="link">
          Twitter
        </a>
      </div>
```
## ~~Error~~ Handling
You might be seeing hydration mismatch error , yes it happens because the ui at client does not match with the ui rendered by the sever. You have to make your theme switch  component client side. There are multiple ways to solve this, you can read it about here [Hyration Mismatch
](https://github.com/pacocoursey/next-themes#avoid-hydration-mismatch) and [Text content does not match server-rendered HTML](https://nextjs.org/docs/messages/react-hydration-error).
I solved it like this, wherever you are importing you theme switch button in the header or footer, import it dynamically.

```jsx
import dynamic from 'next/dynamic'

const ThemeButton = dynamic(() => import('./ThemeButton'), { ssr: false })

const PageHeader = () => {

  return (
    <div className="flex ml-auto">
..................................................
      <ThemeButton />
    </div>
  );
};

export default PageHeader;
```
To avoid [Layout Shift](https://web.dev/cls/), consider rendering a skeleton/placeholder until mounted on the client side.

Thanks for reading, and checkout my [Google Clone with Light/Dark Theme using next-themes](https://sengine.vercel.app/) mode.

_**Happy coding.**_