Dark Mode Tailwind and Next.js 15.1

December 24, 2024 (2w ago)

Adding Dark Mode to your Next.js application not only enhances user experience but also improves accessibility and visual appeal. In this guide, you'll learn step-by-step how to set up a Dark Mode toggle using Next.js 15.1, persist user preferences with localStorage, and leverage Tailwind CSS for seamless theme switching.

Prerequisites

Before we get started, make sure you have the following installed:

  • Node.js
  • Next.js
  • Tailwind CSS
  • npm i lucide-react

Set up Next.js

    npx create-next-app@latest

1. Generate tailwind.config.js: Configure Tailwind CSS for dark mode support.

/** @type {import('tailwindcss').Config} */
/** @type {import('tailwindcss').Config} */
module.exports = {
  darkMode: 'class',
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

2. Generate app/components/theme-provider.tsx: Create a provider to manage and persist theme state.

'use client'

import { createContext, useContext, useEffect, useState } from 'react'

type Theme = 'dark' | 'light'

type ThemeContextType = {
  theme: Theme
  toggleTheme: () => void
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined)

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>('light')

  useEffect(() => {
    const storedTheme = localStorage.getItem('theme') as Theme | null
    if (storedTheme) {
      setTheme(storedTheme)
      if (storedTheme === 'dark') {
        document.documentElement.classList.add('dark')
      }
    } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
      setTheme('dark')
      document.documentElement.classList.add('dark')
    }
  }, [])

  const toggleTheme = () => {
    setTheme((prevTheme) => {
      const newTheme = prevTheme === 'light' ? 'dark' : 'light'
      localStorage.setItem('theme', newTheme)
      if (newTheme === 'dark') {
        document.documentElement.classList.add('dark')
      } else {
        document.documentElement.classList.remove('dark')
      }
      return newTheme
    })
  }

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  const context = useContext(ThemeContext)
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider')
  }
  return context
}

3. Generate app/components/theme-toggle.tsx: Build a toggle button for switching between light and dark modes.

'use client'

import { useTheme } from './theme-provider'
import { Moon, Sun } from 'lucide-react'

export function ThemeToggle() {
  const { theme, toggleTheme } = useTheme()

  return (
    <button
      onClick={toggleTheme}
      className="p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700"
      aria-label="Toggle theme"
    >
      {theme === 'light' ? <Moon size={20} /> : <Sun size={20} />}
    </button>
  )
}

4. Generate app/layout.tsx: Set up a shared layout that integrates the theme provider.

import { ThemeProvider } from './components/theme-provider'
import './globals.css'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  )
}

5. Generate app/page.tsx: Design your main page with support for dark mode styling.

import { ThemeToggle } from './components/theme-toggle'

export default function Home() {
  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24 bg-white dark:bg-gray-900 text-black dark:text-white">
      <h1 className="text-4xl font-bold mb-4">Welcome to Dark Mode</h1>
      <p className="mb-4">Click the button to toggle the theme</p>
      <ThemeToggle />
    </main>
  )
}

With these components in place, you'll have a fully functional Dark Mode toggle in your application!

- Design and development by me.

@2024