darkMode 必须显式设为'class'或'media',否则dark:类会被彻底删除;切换需操作document.documentElement.classList并同步localStorage,初始化脚本须内联于<head>中立即执行。

darkMode 配置必须显式声明为 'class' 或 'media'
不写 darkMode 字段,或写成 darkMode: true、darkMode: 'dark',所有 dark: 类都会在构建时被彻底删除——不是失效,是压根没生成 CSS 规则。Tailwind v3/v4 只接受两个合法值:'class' 或 'media',且必须是 tailwind.config.js 的顶层字段。
常见错误包括:
-
darkMode被误放在theme.extend里 - TypeScript 配置中类型未正确导出,导致配置未加载
- 使用了 JIT 模式但
content字段没包含所有用到dark:的模板文件,导致类名被剔除
切换必须操作 document.documentElement.classList
写 document.body.classList.toggle('dark') 是无效的。Tailwind 默认只检查 document.documentElement(即 <html> 元素)上是否存在 dark 类,<body> 或子元素上的 dark 类完全不触发 dark: 变体。
尤其在 Next.js、Nuxt 等 SSR 框架中,改 body 会导致 hydration mismatch 和首屏闪白。正确做法始终是:
立即学习“前端免费学习笔记(深入)”;
document.documentElement.classList.toggle('dark', isDark)- 切换后必须同步更新
localStorage.setItem('theme', 'dark')或'light',否则刷新即丢失状态 - 多个标签页间不同步?监听
storage事件并响应更新
初始化脚本必须内联在 <head> 中且早于 CSS 解析
“闪白”本质是浏览器先渲染无 dark 类的浅色样式,再等 JS 执行才加类。用 DOMContentLoaded、useEffect 或任何框架生命周期都太晚。
最小可行初始化逻辑(直接贴进 <head>):
(() => {
try {
const stored = localStorage.getItem('theme')
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
const shouldDark = stored === 'dark' || (stored !== 'light' && prefersDark)
document.documentElement.classList.toggle('dark', shouldDark)
} catch {}
})()
注意:
- 不能包裹在异步回调里,必须立即执行
- 要 fallback 到
window.matchMedia,不能默认亮色 - SSR 场景下服务端无法读取
localStorage,需靠初始 HTML 注入 class 或依赖客户端脚本兜底
dark: 类生效的前提是 <html> 有 dark 类
写了 dark:bg-gray-900 却没效果?不是语法错,而是 <html> 根元素上没 dark 类。Tailwind 编译出的规则形如 .dark .dark\:bg-gray-900 { ... },它依赖父级 .dark 存在。
调试时别只看元素面板的 class 列表,重点查 DevTools 的 Computed 标签页——如果 background-color 显示的是 bg-white 对应的规则,说明 .dark .dark\:bg-gray-900 根本没匹配上。
容易被忽略的点:
-
darkMode: 'media'下,dark:完全由系统prefers-color-scheme控制,JS 无法干预 -
darkMode: 'class'下,data-theme="dark"或body.dark都不被识别,必须是html.dark - 混用
dark:bg-gray-900和bg-white在同一元素上时,优先级取决于 CSS 特异性,行为可能难预测


















