darkMode 必须显式设为 'class',否则 dark: 类被完全剔除;切换需操作 document.documentElement,初始化脚本须内联于 防闪白,并配合 localStorage 与 storage 事件实现多标签同步。

tailwind.config.js 的 darkMode 配置必须显式设为 'class'
所有 dark: 类在编译时被完全剔除,不是因为 JS 没写对,而是配置根本没生效。Tailwind v3+ 虽默认倾向 media 策略,但不写 darkMode 字段,或写成 true、'dark'、'',都会让编译器直接忽略全部 dark: 规则——连 warning 都不抛。
正确写法只有一种:
module.exports = {
darkMode: 'class',
content: ['./src/**/*.{js,jsx,ts,tsx}'],
// 其他配置...
}-
darkMode: 'media'→ 完全响应系统偏好,document.documentElement.classList.toggle('dark')无效 -
darkMode: 'class'→ 是唯一支持按钮切换 + localStorage 持久化的方案 - SSR 框架(如 Next.js)中若漏配,首屏 hydration 会失败,出现样式错乱
切换必须操作 document.documentElement,不能碰 body
把 dark 类加到 上是常见错误。Tailwind 默认只检查 元素是否存在 dark 类;加在 body 或任意 div 上,dark:bg-gray-800 永远不会生效。
正确写法始终是:
立即学习“前端免费学习笔记(深入)”;
document.documentElement.classList.toggle('dark')- React 中不要在组件里用
useState控制类名再useEffect同步——状态和 DOM 不同步易导致水合 mismatch - 初始化脚本必须放在
内联执行,否则 CSS 渲染后 JS 才运行,造成“闪白” - 服务端渲染环境需包
try/catch,避免window未定义报错
localStorage + storage 事件同步是跨 tab 必选项
只存不读、只切不监听,用户在另一个标签页切了主题,当前页毫无反应——这不是 bug,是设计缺失。
完整逻辑链必须包含三步:
- 页面加载时:读
localStorage.getItem('theme'),有值就document.documentElement.classList.add('dark');没值则 fallback 到window.matchMedia('(prefers-color-scheme: dark)').matches - 点击按钮时:先
toggle('dark'),再根据当前状态localStorage.setItem('theme', isDark ? 'dark' : 'light') - 监听
window.addEventListener('storage', () => { /* 同步当前页 classList */ }),确保多标签页实时一致
防闪白的关键在于 HTML 渲染前决定 dark 类
浏览器先解析 HTML/CSS,再执行 JS。如果 JS 在 底部或外部文件里,用户会看到 100ms~300ms 的浅色闪屏——尤其在慢网或 SSR 场景下非常明显。
解决办法只有一条:把初始化逻辑写成 IIFE,塞进 的内联 <script></script> 里:
<script>
try {
const theme = localStorage.getItem('theme')
const isDark = theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches)
if (isDark) document.documentElement.classList.add('dark')
} catch (e) {}
</script>这个脚本必须在任何 CSS <link> 之后、 之前执行。否则,无论后续 JS 多完善,首屏都逃不过闪白。


















