DEV Community

Cover image for Easiest way to add theme to Next.js app using Tailwind
Vasu Jhawar
Vasu Jhawar

Posted on • Updated on

Easiest way to add theme to Next.js app using Tailwind

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 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:

npm install next-themes
# or
yarn add next-themes
Enter fullscreen mode Exit fullscreen mode

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.

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

export default function App({ Component, pageProps }) {

 return( 
    <ThemeProvider attribute="class" >
      <Component {...pageProps} />
    </ThemeProvider>
      )
}
Enter fullscreen mode Exit fullscreen mode

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.

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

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

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.

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
Enter fullscreen mode Exit fullscreen mode

Thats it
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.

  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;
Enter fullscreen mode Exit fullscreen mode
  • You can also create cutsom css classes like this, go to globals.css in your directory and in @layer components create the classes.
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components{
    .link{
        @apply hover:underline cursor-pointer dark:text-gray-300 dark:hover:text-orange-500;
    }
}
Enter fullscreen mode Exit fullscreen mode
  • And you those css classes in your components
      <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>
Enter fullscreen mode Exit fullscreen mode

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
and Text content does not match server-rendered HTML.
I solved it like this, wherever you are importing you theme switch button in the header or footer, import it dynamically.

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;
Enter fullscreen mode Exit fullscreen mode

To avoid Layout Shift, 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 mode.

Happy coding.

Top comments (0)