localStorage 是唯一可靠方案,因服务端无法读取主题选择、sessionStorage 仅限当前标签页、cookie 会随请求发送且无前端控制权;必须同步写入、内联初始化、正确处理多标签页同步与 SSR 水合。

必须同步写入 localStorage,否则刷新页面就回到系统偏好或默认浅色——这不是 Bug,是 Tailwind 的预期行为。
为什么 localStorage 是唯一可靠方案
服务端无法读取客户端主题选择,sessionStorage 仅限当前标签页,cookie 会随每次请求发送且无前端直接控制权。只有 localStorage 能跨页面、跨标签页、跨刷新持久保存用户意图。
-
localStorage.setItem('theme', 'dark')必须在每次切换后立即执行,不能延迟到动画结束或异步回调里 - 值建议只用
'dark'、'light'、null(表示跟随系统),避免字符串拼错或大小写不一致 - 不要用
JSON.stringify包一层——localStorage只存字符串,多套一层反而增加解析开销和出错概率
初始化时如何正确读取并应用
脚本必须内联在 里、CSS 加载前执行,否则会出现“闪白”:先渲染亮色,再切暗色。
- 先读
localStorage.getItem('theme'),再 fallback 到window.matchMedia('(prefers-color-scheme: dark)').matches - 判断逻辑要明确优先级:
stored === 'dark'→ 强制暗色;stored === 'light'→ 强制亮色;其他情况(包括null或undefined)才看系统偏好 - 务必用
document.documentElement.classList.toggle('dark', shouldDark),不是body,也不是add/remove
多标签页同步失效怎么办
用户在一个标签页点了切换,另一个标签页还是旧状态——这是常见但容易被忽略的问题。
- 监听
storage事件:window.addEventListener('storage', e => { if (e.key === 'theme') { ... } }) - 事件回调里重新读取
e.newValue,再调用document.documentElement.classList.toggle('dark', e.newValue === 'dark') - 注意:该事件不会在触发变更的标签页自身触发,只通知其他打开的同源标签页
- 如果用了
sessionStorage做临时缓存,它不触发storage事件,无法同步
SSR 框架里怎么避免水合不匹配
Next.js、Nuxt 等框架服务端渲染时没有 window,直接操作 document 会报错,且首屏 HTML 不含 dark 类会导致水合失败。
- 服务端应根据请求头或用户配置 fallback 到系统偏好(实际不可知),输出
或—— 但更稳妥的是服务端不设 class,靠客户端脚本接管 - 客户端初始化脚本仍需内联在
,且不能包裹在useEffect或DOMContentLoaded中 - React 场景下,首次渲染时组件 props 不应依赖 theme 状态,而应等 DOM class 设置完成后再 re-render
最容易被忽略的点:localStorage 读写本身不触发 UI 更新,你得手动同步 document.documentElement.classList 和存储值,缺一不可。任何一步漏掉,都会导致“点了开关没反应”或“刷新后还原”。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











