Next.js Dark Mode with Tailwind CSS

May 16, 2023

Follow the steps below to add dark mode to your Next.js application using Tailwind CSS

Install Tailwind

If you don't have Tailwind already installed, run the following command to install it and its dependencies.

npm install -D tailwindcss postcss autoprefixer

Run the init command to generate both tailwind.config.js and postcss.config.js.

npx tailwindcss init -p

Add the paths to all of your template files in your tailwind.config.js file.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
 
    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Add the @tailwind directives at the top of your globals.css file.

@tailwind base;
@tailwind components;
@tailwind utilities;

Initial setup

setup the darkMode strategy to class in the tailwind.config.js file

module.exports = {
  darkMode: 'class',
  // ...
}

Install next-themes

npm i next-themes

Create a theme provider

"use client"
 
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
import * as React from "react"
 
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

Wrap your root layout

import { ThemeProvider } from "@/components/theme-provider"
 
export default function RootLayout({ children }: RootLayoutProps) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head />
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  )
}

add the following to your global.css

@layer base {
   :root {
      --background: 0 0% 100%;
      --foreground: 0 0% 3.9%;
 
      --card: 0 0% 100%;
      --card-foreground: 0 0% 3.9%;
 
      --popover: 0 0% 100%;
      --popover-foreground: 0 0% 3.9%;
 
      --primary: 0 0% 9%;
      --primary-foreground: 0 0% 98%;
 
      --secondary: 0 0% 96.1%;
      --secondary-foreground: 0 0% 9%;
 
      --muted: 0 0% 96.1%;
      --muted-foreground: 0 0% 45.1%;
 
      --accent: 0 0% 96.1%;
      --accent-foreground: 0 0% 9%;
 
      --destructive: 0 84.2% 60.2%;
      --destructive-foreground: 0 0% 98%;
 
      --border: 0 0% 89.8%;
      --input: 0 0% 89.8%;
      --ring: 0 0% 3.9%;
   }
 
   .dark {
      --background: 0 0% 0%;
      --foreground: 0 0% 98%;
 
      --card: 0 0% 3.9%;
      --card-foreground: 0 0% 98%;
 
      --popover: 0 0% 3.9%;
      --popover-foreground: 0 0% 98%;
 
      --primary: 0 0% 98%;
      --primary-foreground: 0 0% 9%;
 
      --secondary: 0 0% 14.9%;
      --secondary-foreground: 0 0% 98%;
 
      --muted: 0 0% 14.9%;
      --muted-foreground: 0 0% 63.9%;
 
      --accent: 0 0% 14.9%;
      --accent-foreground: 0 0% 98%;
 
      --destructive: 0 62.8% 30.6%;
      --destructive-foreground: 0 0% 98%;
 
      --border: 0 0% 14.9%;
      --input: 0 0% 14.9%;
      --ring: 0 0% 83.1%;
   }
}
 
@layer base {
   body {
      @apply bg-background text-foreground;
   }
}

Adding a toggle button

"use client"
import { useTheme } from "next-themes"
import * as React from "react"
type IconProps = React.HTMLAttributes<SVGElement>
 
export function ModeToggle() {
  const { setTheme, theme } = useTheme()
 
  return (
    <button
      className="inline-flex items-center justify-center whitespace-nowrap px-3 hover:opacity-80"
      onClick={() => setTheme(theme === "light" ? "dark" : "light")}
    >
      <Icons.sun className="dark:-rotate-90 size-5 rotate-0 scale-100 transition-all dark:scale-0" />
      <Icons.moon className="absolute size-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
      <span className="sr-only">Toggle theme</span>
    </button>
  )
}
 
export const Icons = {
  sun: (props: IconProps) => (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <circle cx="12" cy="12" r="4" />
      <path d="M12 2v2" />
      <path d="M12 20v2" />
      <path d="m4.93 4.93 1.41 1.41" />
      <path d="m17.66 17.66 1.41 1.41" />
      <path d="M2 12h2" />
      <path d="M20 12h2" />
      <path d="m6.34 17.66-1.41 1.41" />
      <path d="m19.07 4.93-1.41 1.41" />
    </svg>
  ),
  moon: (props: IconProps) => (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      {...props}
    >
      <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
    </svg>
  )
}

Done! Try it out!