PAYATHON 2026

Tailwind CSS Dark Mode 切换后浅色模式颜色异常

架构老王

结论

这个问题通常有两个互不相关的原因:

  1. 如果项目使用 Tailwind CSS v4,tailwind.config.js 中的 darkMode: 'class' 很可能没有参与当前的 CSS 构建。此时,dark: 默认仍由系统的 prefers-color-scheme: dark 控制。即使移除 <html> 上的 dark class,页面也不会切回浅色模式。
  2. 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 后,还需要重新启动开发服务器,以免构建工具继续使用旧配置。

备注:内容仅供参考。