
Nextjs TailwindCSS Tutorial: Add darkmode to your app
Install dependencies
Install the necessary packages for the project using the following commands. The first command adds various libraries to handle class names, animations, and theme management, while the second command installs a package for merging Tailwind CSS utilities as a development dependency.
yarn add clsx class-variance-authority lucide-react next-themes tailwind-mergeyarn add tailwind-css-merge --devTailwindcss Config
Configure Tailwind CSS for the project with this snippet. Enable dark mode, specify the locations of content files, and extend the default theme by adding colors. An animation plugin is also included for extra functionality (optional).
//tailwind.config.ts
import type { Config } from 'tailwindcss'
import { fontFamily } from 'tailwindcss/defaultTheme'
const config: Config = {
darkMode: "class",
content: ["src/app/\*\*/\*.{ts,tsx}", "src/components/\*\*/\*.{ts,tsx}"],
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
},
},
},
plugins: [require("tailwindcss-animate")],
}
export default configUtils
Define a utility function that combines class names with this code. Utilize the clsx and tailwind-merge packages to allow a concise and flexible way to apply class names throughout the application.
//src/lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}Button component for theme toggling
Create a reusable Button component using the class-variance-authority package with this code snippet. Define different variants and sizes for a consistent design across the application. This Button component will be used later for toggling the theme.
//src/components/ui/Button.ts
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
ghost: "bg-transparent text-primary hover:bg-primary/10",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-3 rounded-md",
lg: "h-11 px-8 rounded-md",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }Theme Provider
Introduce the ThemeProvider component with this code, leveraging the next-themes package to handle theme state in the Next.js application. Wrap components with this provider to enable them to access and modify the theme.
//components/ThemeProvider.tsx
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}Theme Toggle component
Define a ThemeToggle component using the previously created Button component with this snippet. Include icons for both light and dark modes and toggle the theme when clicked, offering a user-friendly way to switch between themes.
//src/components/ThemeToggle.tsx
"use client"
import * as React from "react"
import { Sun, Moon } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/Button"
export function ThemeToggle() {
const { setTheme, theme } = useTheme()
return (
<Button
variant="ghost"
size="icon"
className="mb-4"
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
>
<Sun className="h-[1.5rem] w-[1.3rem] dark:hidden" />
<Moon className="hidden h-5 w-5 dark:block" />
<span className="sr-only">Toggle theme</span>
</Button>
)
}Putting it all together
Integrate Tailwind CSS and define custom variables for colors with this global CSS file. Set up the default and dark theme styles, linking the Tailwind classes to the custom color variables. The changes in the colors are applied when the dark class is activated.
//src/app/globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
}
}
@layer base {
body {
@apply bg-background text-foreground;
}
}Layout.tsx
Construct the main layout for the application with this code, including the theme provider, theme toggle button, and content area. Integrate the global CSS, add meta-information, and set up the layout to be responsive and visually appealing.
//src/app/layout.tsx
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { ThemeProvider } from "@/components/ThemeProvider"
import { ThemeToggle } from '@/components/ThemeToggle'
import { cn } from '@/lib/utils'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "white" },
{ media: "(prefers-color-scheme: dark)", color: "black" },
],
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" suppressHydrationWarning>
<body className={cn(
"min-h-screen bg-background font-sans antialiased",
inter.className
)}>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<main className="py-12 max-w-4xl mx-auto">
<div>
<ThemeToggle />
</div>
{children}
</main>
</ThemeProvider>
</body>
</html>
)
}
Render text
Since we already added dark mode to our site, the paragraph will have dark color in light mode and light color in dark mode.
//src/app/page.tsx
export default function Home() {
return (
<p>Hello world</p>
)
}Output
Dark mode

Light mode
<...