First, why do we need to implement dark and light themes in our projects?
Having this feature is not a requirement that will necessarily bring more traffic to your project, but it greatly improves the user experience, as people have different preferences.
With that understood, let's dive into how to do it! 😄
Understanding the feature in Tailwind
To configure this feature with Tailwind, you just need to add or remove the Example: In this example, the default background color will be In Next.js, it’s a bit different, because you’ll likely need some help from a library called Let’s see examples both without and with the library. In this case, you are responsible for: And using it in your root layout: When using the Having dark and light themes in your system is not absolutely critical, but it is very important to improve usability and the overall user interaction with your project.dark
class on the element and use the
dark:
prefix in the elements’ classes that you want to style differently based on the theme.
bg-gray-800
(light mode), and when the dark theme is active, it will switch to bg-gray-500
.
How it works in Next.js
next-themes
.
Important: it’s not mandatory — you can achieve the same functionality manually — but using the library makes the development process much easier.
Example without using the library
localStorage
;// hooks/useTheme.ts
import { useEffect, useState } from 'react';
type Theme = 'light' | 'dark';
export function useTheme() {
const [theme, setTheme] = useState('light');
// Detect system preferences
useEffect(() => {
const savedTheme = localStorage.getItem('theme') as Theme | null;
if (savedTheme) {
setTheme(savedTheme);
} else {
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
setTheme(prefersDark ? 'dark' : 'light');
}
}, []);
// Update class and save to localStorage
useEffect(() => {
const root = document.documentElement;
if (theme === 'dark') {
root.classList.add('dark');
root.classList.remove('light');
} else {
root.classList.add('light');
root.classList.remove('dark');
}
localStorage.setItem('theme', theme);
}, [theme]);
return { theme, setTheme };
}
Example using the
next-themes
library
// provider/theme-provider.ts
'use client'
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import * as React from 'react'
function ThemeProvider({ children, ...props }: React.ComponentProps) {
const [mounted, setMounted] = React.useState(false)
React.useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return <>{children}>
}
return {children}
}
export { ThemeProvider }
async function RootLayout({ children }: { children: ReactNode }) {
return (
{children}
)
}
next-themes
library, you get many built-in features like:
defaultTheme="system"
, which automatically picks up the user’s system preference;class
attribute;
Conclusion