darkmode必须配置为顶层字段且仅支持'class'或'media',否则dark类根本不会生成;切换时需直接操作document.documentelement.classlist并同步localstorage,ssr需内联立即执行脚本处理。

tailwind.config.js 里 darkMode 没配或配错了
dark: 类根本没生成,不是失效,是压根没进 CSS 文件——连警告都不会抛。常见错法:darkMode: true、darkMode: 'dark'、darkMode 写在 theme.extend 里,或者 TypeScript 配置类型没导出导致整个 config 没加载。
必须写成顶层字段,且只接受两个值:darkMode: 'class' 或 darkMode: 'media'。v4 没新增选项,也没默认值。
- 选
'class':后续所有切换逻辑都得靠 JS 操作document.documentElement.classList - 选
'media':CSS 自动响应prefers-color-scheme,但无法手动干预,也不读 localStorage
切换时改了 body 而不是 html
document.body.classList.toggle('dark') 看似合理,实际无效。Tailwind 默认只检查 document.documentElement(即 元素)上是否有 dark 类。SSR 框架(Next.js/Nuxt)会因 hydration mismatch 直接报错或首屏闪白。
正确写法只有一行:document.documentElement.classList.toggle('dark')。别封装、别代理、别加 wrapper 元素——直接干。
- 如果用了自定义
darkSelector(比如插件改成了my-dark-mode),那也要对应改 class 名,不能硬写dark - SSR 场景下,服务端渲染时
window不存在,JS 初始化必须包裹if (typeof window !== 'undefined'),但初始化脚本本身仍需内联于
初始化脚本太晚执行,导致“闪白”
把初始化逻辑塞进 useEffect、DOMContentLoaded 或 window.onload,页面已经按亮色渲染完了,JS 才去加 dark 类——这就是肉眼可见的“闪一下”。
必须把判断逻辑写成立即执行函数,内联在 里、<link rel="stylesheet"> 之前:
(() => {
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 {}
})()
- 这个脚本不依赖任何框架,兼容所有环境
- 它处理了三种状态:强制深色(
theme=dark)、强制浅色(theme=light)、跟随系统(theme为空或其它值) - 漏掉
try/catch可能在无 localStorage 环境(如某些 WebView)崩溃
localStorage 和 DOM 状态不同步
只改 document.documentElement.classList,不存 localStorage.setItem('theme', 'dark'),刷新就回退;只存 localStorage,不改 DOM 类,当前页就不变——两者必须原子性同步。
切换函数里这两步缺一不可:
document.documentElement.classList.toggle('dark')localStorage.setItem('theme', isDark ? 'dark' : 'light')
另外,跨标签页不同步也是常见问题:一个页面切了深色,另一个还亮着。得监听 storage 事件,在当前页响应其他页的状态变更。
最容易被忽略的是:v4 依然不支持 data-theme="dark" 这类自定义属性,也不识别 body.dark,更不会自动 fallback 到系统偏好——它只认你亲手塞进 的那个 dark 类,早一秒、晚一秒、错一个字母,都不生效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











