Next.js Dark Mode with Tailwind CSS

May 16, 2023

Follow the steps below 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.

1npm install -D tailwindcss postcss autoprefixer
1npm install -D tailwindcss postcss autoprefixer

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

1npx tailwindcss init -p
1npx tailwindcss init -p

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

1/** @type {import('tailwindcss').Config} */
2module.exports = {
3 content: [
4 "./app/**/*.{js,ts,jsx,tsx,mdx}",
5 "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 "./components/**/*.{js,ts,jsx,tsx,mdx}",
7
8 // Or if using `src` directory:
9 "./src/**/*.{js,ts,jsx,tsx,mdx}",
10 ],
11 theme: {
12 extend: {},
13 },
14 plugins: [],
15}
1/** @type {import('tailwindcss').Config} */
2module.exports = {
3 content: [
4 "./app/**/*.{js,ts,jsx,tsx,mdx}",
5 "./pages/**/*.{js,ts,jsx,tsx,mdx}",
6 "./components/**/*.{js,ts,jsx,tsx,mdx}",
7
8 // Or if using `src` directory:
9 "./src/**/*.{js,ts,jsx,tsx,mdx}",
10 ],
11 theme: {
12 extend: {},
13 },
14 plugins: [],
15}

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

1@tailwind base;
2@tailwind components;
3@tailwind utilities;
1@tailwind base;
2@tailwind components;
3@tailwind utilities;

Initial setup

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

1module.exports = {
2 darkMode: 'class',
3 // ...
4}
1module.exports = {
2 darkMode: 'class',
3 // ...
4}

Install next-themes

1npm i next-themes
1npm i next-themes

Create a theme provider

1"use client"
2
3import { ThemeProvider as NextThemesProvider } from "next-themes"
4import { type ThemeProviderProps } from "next-themes/dist/types"
5import * as React from "react"
6
7export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 return <NextThemesProvider {...props}>{children}</NextThemesProvider>
9}
1"use client"
2
3import { ThemeProvider as NextThemesProvider } from "next-themes"
4import { type ThemeProviderProps } from "next-themes/dist/types"
5import * as React from "react"
6
7export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 return <NextThemesProvider {...props}>{children}</NextThemesProvider>
9}

Wrap your root layout

1import { ThemeProvider } from "@/components/theme-provider"
2
3export default function RootLayout({ children }: RootLayoutProps) {
4 return (
5 <html lang="en" suppressHydrationWarning>
6 <head />
7 <body>
8 <ThemeProvider
9 attribute="class"
10 defaultTheme="system"
11 enableSystem
12 disableTransitionOnChange
13 >
14 {children}
15 </ThemeProvider>
16 </body>
17 </html>
18 )
19}
1import { ThemeProvider } from "@/components/theme-provider"
2
3export default function RootLayout({ children }: RootLayoutProps) {
4 return (
5 <html lang="en" suppressHydrationWarning>
6 <head />
7 <body>
8 <ThemeProvider
9 attribute="class"
10 defaultTheme="system"
11 enableSystem
12 disableTransitionOnChange
13 >
14 {children}
15 </ThemeProvider>
16 </body>
17 </html>
18 )
19}

add the following to your global.css

1@layer base {
2 :root {
3 --background: 0 0% 100%;
4 --foreground: 0 0% 3.9%;
5
6 --card: 0 0% 100%;
7 --card-foreground: 0 0% 3.9%;
8
9 --popover: 0 0% 100%;
10 --popover-foreground: 0 0% 3.9%;
11
12 --primary: 0 0% 9%;
13 --primary-foreground: 0 0% 98%;
14
15 --secondary: 0 0% 96.1%;
16 --secondary-foreground: 0 0% 9%;
17
18 --muted: 0 0% 96.1%;
19 --muted-foreground: 0 0% 45.1%;
20
21 --accent: 0 0% 96.1%;
22 --accent-foreground: 0 0% 9%;
23
24 --destructive: 0 84.2% 60.2%;
25 --destructive-foreground: 0 0% 98%;
26
27 --border: 0 0% 89.8%;
28 --input: 0 0% 89.8%;
29 --ring: 0 0% 3.9%;
30
31 --radius: 0.5rem;
32 }
33
34 .dark {
35 --background: 0 0% 0%;
36 --foreground: 0 0% 98%;
37
38 --card: 0 0% 3.9%;
39 --card-foreground: 0 0% 98%;
40
41 --popover: 0 0% 3.9%;
42 --popover-foreground: 0 0% 98%;
43
44 --primary: 0 0% 98%;
45 --primary-foreground: 0 0% 9%;
46
47 --secondary: 0 0% 14.9%;
48 --secondary-foreground: 0 0% 98%;
49
50 --muted: 0 0% 14.9%;
51 --muted-foreground: 0 0% 63.9%;
52
53 --accent: 0 0% 14.9%;
54 --accent-foreground: 0 0% 98%;
55
56 --destructive: 0 62.8% 30.6%;
57 --destructive-foreground: 0 0% 98%;
58
59 --border: 0 0% 14.9%;
60 --input: 0 0% 14.9%;
61 --ring: 0 0% 83.1%;
62 }
63}
64
65@layer base {
66 body {
67 @apply bg-background text-foreground;
68 }
69}
1@layer base {
2 :root {
3 --background: 0 0% 100%;
4 --foreground: 0 0% 3.9%;
5
6 --card: 0 0% 100%;
7 --card-foreground: 0 0% 3.9%;
8
9 --popover: 0 0% 100%;
10 --popover-foreground: 0 0% 3.9%;
11
12 --primary: 0 0% 9%;
13 --primary-foreground: 0 0% 98%;
14
15 --secondary: 0 0% 96.1%;
16 --secondary-foreground: 0 0% 9%;
17
18 --muted: 0 0% 96.1%;
19 --muted-foreground: 0 0% 45.1%;
20
21 --accent: 0 0% 96.1%;
22 --accent-foreground: 0 0% 9%;
23
24 --destructive: 0 84.2% 60.2%;
25 --destructive-foreground: 0 0% 98%;
26
27 --border: 0 0% 89.8%;
28 --input: 0 0% 89.8%;
29 --ring: 0 0% 3.9%;
30
31 --radius: 0.5rem;
32 }
33
34 .dark {
35 --background: 0 0% 0%;
36 --foreground: 0 0% 98%;
37
38 --card: 0 0% 3.9%;
39 --card-foreground: 0 0% 98%;
40
41 --popover: 0 0% 3.9%;
42 --popover-foreground: 0 0% 98%;
43
44 --primary: 0 0% 98%;
45 --primary-foreground: 0 0% 9%;
46
47 --secondary: 0 0% 14.9%;
48 --secondary-foreground: 0 0% 98%;
49
50 --muted: 0 0% 14.9%;
51 --muted-foreground: 0 0% 63.9%;
52
53 --accent: 0 0% 14.9%;
54 --accent-foreground: 0 0% 98%;
55
56 --destructive: 0 62.8% 30.6%;
57 --destructive-foreground: 0 0% 98%;
58
59 --border: 0 0% 14.9%;
60 --input: 0 0% 14.9%;
61 --ring: 0 0% 83.1%;
62 }
63}
64
65@layer base {
66 body {
67 @apply bg-background text-foreground;
68 }
69}

Adding a toggle button

1"use client"
2import { useTheme } from "next-themes"
3import * as React from "react"
4type IconProps = React.HTMLAttributes<SVGElement>
5
6export function ModeToggle() {
7 const { setTheme, theme } = useTheme()
8
9 return (
10 <button
11 className="inline-flex items-center justify-center whitespace-nowrap px-3 hover:opacity-80"
12 onClick={() => setTheme(theme === "light" ? "dark" : "light")}
13 >
14 <Icons.sun className="dark:-rotate-90 size-5 rotate-0 scale-100 transition-all dark:scale-0" />
15 <Icons.moon className="absolute size-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
16 <span className="sr-only">Toggle theme</span>
17 </button>
18 )
19}
20
21export const Icons = {
22 sun: (props: IconProps) => (
23 <svg
24 xmlns="http://www.w3.org/2000/svg"
25 width="24"
26 height="24"
27 viewBox="0 0 24 24"
28 fill="none"
29 stroke="currentColor"
30 strokeWidth="2"
31 strokeLinecap="round"
32 strokeLinejoin="round"
33 {...props}
34 >
35 <circle cx="12" cy="12" r="4" />
36 <path d="M12 2v2" />
37 <path d="M12 20v2" />
38 <path d="m4.93 4.93 1.41 1.41" />
39 <path d="m17.66 17.66 1.41 1.41" />
40 <path d="M2 12h2" />
41 <path d="M20 12h2" />
42 <path d="m6.34 17.66-1.41 1.41" />
43 <path d="m19.07 4.93-1.41 1.41" />
44 </svg>
45 ),
46 moon: (props: IconProps) => (
47 <svg
48 xmlns="http://www.w3.org/2000/svg"
49 width="24"
50 height="24"
51 viewBox="0 0 24 24"
52 fill="none"
53 stroke="currentColor"
54 strokeWidth="2"
55 strokeLinecap="round"
56 strokeLinejoin="round"
57 {...props}
58 >
59 <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
60 </svg>
61 )
62}
1"use client"
2import { useTheme } from "next-themes"
3import * as React from "react"
4type IconProps = React.HTMLAttributes<SVGElement>
5
6export function ModeToggle() {
7 const { setTheme, theme } = useTheme()
8
9 return (
10 <button
11 className="inline-flex items-center justify-center whitespace-nowrap px-3 hover:opacity-80"
12 onClick={() => setTheme(theme === "light" ? "dark" : "light")}
13 >
14 <Icons.sun className="dark:-rotate-90 size-5 rotate-0 scale-100 transition-all dark:scale-0" />
15 <Icons.moon className="absolute size-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
16 <span className="sr-only">Toggle theme</span>
17 </button>
18 )
19}
20
21export const Icons = {
22 sun: (props: IconProps) => (
23 <svg
24 xmlns="http://www.w3.org/2000/svg"
25 width="24"
26 height="24"
27 viewBox="0 0 24 24"
28 fill="none"
29 stroke="currentColor"
30 strokeWidth="2"
31 strokeLinecap="round"
32 strokeLinejoin="round"
33 {...props}
34 >
35 <circle cx="12" cy="12" r="4" />
36 <path d="M12 2v2" />
37 <path d="M12 20v2" />
38 <path d="m4.93 4.93 1.41 1.41" />
39 <path d="m17.66 17.66 1.41 1.41" />
40 <path d="M2 12h2" />
41 <path d="M20 12h2" />
42 <path d="m6.34 17.66-1.41 1.41" />
43 <path d="m19.07 4.93-1.41 1.41" />
44 </svg>
45 ),
46 moon: (props: IconProps) => (
47 <svg
48 xmlns="http://www.w3.org/2000/svg"
49 width="24"
50 height="24"
51 viewBox="0 0 24 24"
52 fill="none"
53 stroke="currentColor"
54 strokeWidth="2"
55 strokeLinecap="round"
56 strokeLinejoin="round"
57 {...props}
58 >
59 <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" />
60 </svg>
61 )
62}

Done! Try it out!