Tailwind CSS Dark Mode 切换后浅色模式颜色异常
架构老王
结论
这个问题通常有两个互不相关的原因:
- 如果项目使用 Tailwind CSS v4,
tailwind.config.js中的darkMode: 'class'很可能没有参与当前的 CSS 构建。此时,dark:默认仍由系统的prefers-color-scheme: dark控制。即使移除<html>上的darkclass,页面也不会切回浅色模式。 text-headingColor不是有效的颜色工具类。当前配置只定义了headingColor.light和headingColor.dark,所以 Tailwind 会生成text-headingColor-light和text-headingColor-dark,不会生成text-headingColor。
另外,border-gray-700、bg-indigo-500、text-white 等类没有对应的 dark: 或浅色样式,本身就不会随主题切换。
如果使用 Tailwind CSS v4
在入口 CSS 中明确将 dark: 定义为 class selector:
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--color-heading-light: #4f46e5;
--color-heading-dark: #a5b4fc;
}
之后可以这样使用:
<h2 className="text-heading-light dark:text-heading-dark">
API Pricing
</h2>
生成的暗色样式会由 .dark class 控制,而不是跟随操作系统主题。
主题切换代码可以保留,建议改成函数式更新:
import { useEffect, useState } from "react";
const ThemeToggle = () => {
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
document.documentElement.classList.toggle("dark", isDarkMode);
}, [isDarkMode]);
return (
<button
type="button"
onClick={() => setIsDarkMode((current) => !current)}
className="rounded-full bg-gray-200 p-2 dark:bg-gray-800"
aria-label="Toggle Dark Mode"
>
{/* SVG Icons */}
</button>
);
};
export default ThemeToggle;
如果仍想使用 JavaScript 配置文件,需要在 v4 的入口 CSS 中显式加载配置,并确保路径正确:
@config "../tailwind.config.js";
@import "tailwindcss";
不过在 v4 中,直接使用 @custom-variant 和 @theme 会更直观。
如果使用 Tailwind CSS v3
当前的 darkMode: "class" 配置没有问题:
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
darkMode: "class",
theme: {
extend: {
colors: {
headingColor: {
light: "#4F46E5",
dark: "#A5B4FC",
},
},
},
},
plugins: [],
};
问题出在组件中的这一行:
<h2 className="text-xl font-semibold mb-2 text-headingColor">
颜色对象中没有定义 DEFAULT,因此应改为:
<h2 className="mb-2 text-xl font-semibold text-headingColor-light dark:text-headingColor-dark">
API Pricing
</h2>
也可以为颜色添加 DEFAULT:
colors: {
headingColor: {
DEFAULT: "#4F46E5",
light: "#4F46E5",
dark: "#A5B4FC",
},
},
这样就可以使用:
<h2 className="text-headingColor dark:text-headingColor-dark">
API Pricing
</h2>
修正后的 Pricing 组件
const Pricing = () => {
return (
<div className="mx-auto max-w-3xl rounded-lg border border-gray-300 bg-white p-8 text-black shadow-lg dark:border-[#7B8595] dark:bg-gray-800 dark:text-white">
<h2 className="mb-2 text-xl font-semibold text-headingColor-light dark:text-headingColor-dark">
API Pricing
</h2>
<p className="mb-6 text-sm">
Our API pricing is based on the model used and the number of tokens
processed. Here's a breakdown of the costs:
</p>
<div className="overflow-x-auto">
<table className="w-full overflow-hidden rounded-xl text-left text-sm">
<thead>
<tr className="bg-indigo-500 text-white dark:bg-indigo-700">
<th className="p-3 font-semibold">API</th>
<th className="p-3 font-semibold">MODEL</th>
<th className="p-3 font-semibold">PRICE PER 1K TOKENS</th>
</tr>
</thead>
<tbody>
<tr className="border-t border-gray-200 dark:border-gray-700">
<td className="p-3">OpenAI</td>
<td className="p-3">GPT-3.5</td>
<td className="p-3">$0.002</td>
</tr>
<tr className="border-t border-gray-200 dark:border-gray-700">
<td className="p-3">OpenAI</td>
<td className="p-3">GPT-4</td>
<td className="p-3">$0.03</td>
</tr>
<tr className="border-t border-gray-200 dark:border-gray-700">
<td className="p-3">Together AI</td>
<td className="p-3">Llama-2-70b</td>
<td className="p-3">$0.0008</td>
</tr>
<tr className="border-t border-gray-200 dark:border-gray-700">
<td className="p-3">Together AI</td>
<td className="p-3">Llama-2-13b</td>
<td className="p-3">$0.0006</td>
</tr>
</tbody>
</table>
</div>
<div className="mt-6">
<h3 className="mb-2 text-lg font-semibold text-headingColor-light dark:text-headingColor-dark">
Token Estimation
</h3>
<p className="mb-4 text-sm">
On average, 1 token is approximately 4 characters or 0.75 words. For
precise pricing, we recommend using our token calculator tool.
</p>
<h3 className="mb-2 text-lg font-semibold text-headingColor-light dark:text-headingColor-dark">
Billing
</h3>
<p className="text-sm">
You will only be charged for the tokens used in generating the book.
The API tracks token usage and bills accordingly. Detailed usage
reports are available in your account dashboard.
</p>
</div>
</div>
);
};
export default Pricing;
检查方法
点击切换按钮后,在浏览器开发者工具中检查 <html>:
<html class="dark">
再次点击后,它应该变成:
<html>
如果 class 能够正常添加和删除,但删除后 dark: 样式仍然生效,基本可以判断生成的 dark: 仍采用媒体查询模式。此时需要检查 Tailwind 版本和入口 CSS。添加 !important 解决不了这个问题,因为这不是样式优先级冲突,而是暗色规则的触发条件仍然成立。
修改 Tailwind 配置或入口 CSS 后,还需要重新启动开发服务器,以免构建工具继续使用旧配置。
备注:内容仅供参考。
