Dark Mode with Tailwind v4 & next-themes

Published: (January 10, 2026 at 02:10 PM EST)
2 min read
Source: Dev.to

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

PropDescription
attribute="class"Adds the dark class to the “ element
defaultTheme="system"Uses the system preference by default
enableSystemDetects 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- or bg- 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();
PropertyTypeDescription
themestringCurrent theme: "light", "dark", or "system"
setTheme(name)functionChange the theme
systemThemestringSystem preference: "light" or "dark"
themesarrayAvailable 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 localStorage and refresh the page.
    • Ensure the @variant dark (&:is(.dark *)); rule is present in your compiled CSS.
  • Colors not changing

    • Do not include text- or bg- in the custom color variable names.
    • Use the correct light-dark syntax, e.g., light-dark(#fff, #000).

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

Back to Blog

Related posts

Read more »