I've got the light mode switch working, but now Tailwind's 'dark:' class modifier does not work.
Following these instructions: Implementing Dark Mode and Theme Switching using Tailwind v4 and Next.js
Reproduce (enter through npx create):
npx create-next-app@latest
npm install tailwindcss@next @tailwindcss/postcss@next
npm install next-themes
Global CSS (replace @tailwind lines for tailwind 4):
./src/app/global.css
@import "tailwindcss";
@variant dark (&:where([data-theme="dark"]));
./postcss.config.mjs
export default {
plugins: {
'@tailwindcss/postcss': {},
},
};
./src/components/theme/ThemeSelect.jsx
'use client'
import { useTheme } from 'next-themes'
export default function ThemeSelect() {
const { theme, setTheme } = useTheme();
return (
<select title='theme switcher'
value={theme}
onChange={(e) => setTheme(e.target.value)}
>
<option value="system">System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
);
}
Layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "next-themes";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider attribute="data-theme" enableSystem>
{children}
</ThemeProvider>
</body>
</html>
);
}
./src/app/page.jsx
import Image from "next/image";
import ThemeSelect from "../components/theme/ThemeSelect"
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ThemeSelect />
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
src/app/page.tsx
</code>
.
</li>
<li className='text-black dark:text-green-400'>Save and see your changes instantly.</li>
</ol>
</main>
</div>
);
}
dark:invert
and text-black dark:text-green-400
have no effect
Your variant configuration:
@variant dark (&:where([data-theme="dark"]));
Would only match if the element itself had data-theme="dark"
on it, so dark:
would only work like this:
<div class="text-black dark:text-green-400" data-theme="dark">
Green
<span class="text-black dark:text-green-400">Black</span>
</div>
However, it seems like you'd like dark:
to apply when a parent has data-theme="dark"
on it. In which case, you should change your @variant
rule to be:
@variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
The [data-theme="dark"] *
candidate in the :where()
matches if a parent has data-theme="dark"
:
<div class="text-black dark:text-green-400" data-theme="dark">
Green
<span class="text-black dark:text-green-400">Green</span>
</div>