Dark Mode with Tailwind v4 & next-themes
Source: Dev.to
Installation
npm install next-themes
Setup in main.jsx
import { ThemeProvider } from "next-themes";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(
);
Provider Props
| Prop | Description |
|---|---|
attribute="class" | Adds the dark class to the “ element |
defaultTheme="system" | Uses the system preference by default |
enableSystem | Detects system theme automatically |
storageKey="theme-name" | (Optional) Custom localStorage key |
Tailwind CSS Configuration
@import "tailwindcss";
/* Enable dark variant */
@variant dark (&:is(.dark *));
/* Define custom color tokens */
@theme {
--color-primary: light-dark(#10b981, #dc2626);
--color-surface: light-dark(#f8fafc, #0f172a);
}
Syntax for color tokens
--color-[name]: light-dark([light-color], [dark-color]);
Example
--color-primary: light-dark(#10b981, #dc2626);
Using the Tokens in JSX
Hello
Content
Note: Do not include
text-orbg-in the variable names; Tailwind adds those prefixes automatically.
Theme Toggle Component
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";
function ThemeToggle() {
const { theme, setTheme } = useTheme();
const [mounted, setMounted] = useState(false);
// Prevent mismatched SSR rendering
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return (
setTheme(theme === "dark" ? "light" : "dark")}>
Toggle Theme
);
}
A minimal version without the mount guard:
import { useTheme } from "next-themes";
function ThemeToggle() {
const { theme, setTheme } = useTheme();
return (
setTheme(theme === "dark" ? "light" : "dark")}>
{theme === "dark" ? "🌙" : "☀️"}
);
}
useTheme API Reference
const { theme, setTheme, systemTheme, themes } = useTheme();
| Property | Type | Description |
|---|---|---|
theme | string | Current theme: "light", "dark", or "system" |
setTheme(name) | function | Change the theme |
systemTheme | string | System preference: "light" or "dark" |
themes | array | Available themes |
Extended Color Tokens
@theme {
/* Backgrounds */
--color-surface: light-dark(#f8fafc, #0f172a);
--color-card: light-dark(#ffffff, #1e293b);
/* Text */
--color-primary: light-dark(#1f2937, #f9fafb);
--color-secondary: light-dark(#6b7280, #9ca3af);
/* Brand */
--color-brand: light-dark(#10b981, #34d399);
--color-accent: light-dark(#3b82f6, #60a5fa);
}
Troubleshooting
-
Dark mode not activating
- Verify that “ appears in DevTools.
- Clear
localStorageand refresh the page. - Ensure the
@variant dark (&:is(.dark *));rule is present in your compiled CSS.
-
Colors not changing
- Do not include
text-orbg-in the custom color variable names. - Use the correct
light-darksyntax, e.g.,light-dark(#fff, #000).
- Do not include
Quick Reference
// Setup
// Toggle theme
const { theme, setTheme } = useTheme();
setTheme(theme === "dark" ? "light" : "dark");
// Define colors
--color-primary: light-dark(#10b981, #dc2626);
// Use colors
…
Links