data-theme 属性必须设在 document.documentelement 上,因为 :root 对应 html 元素,css 变量定义在此才能全局生效;设在 body 或容器会导致 fixed 元素、伪元素等颜色不一致。

data-theme 属性必须设在 document.documentElement 上
很多人把 data-theme 加到 或某个容器上,结果顶部导航、fixed 元素或伪元素颜色不一致——因为 CSS 变量定义在 :root,而 :root 对应的就是 html 元素。只有改 document.documentElement,才能让所有层级的 var(--xxx) 统一响应。
常见错误现象:document.body.setAttribute('data-theme', 'dark') 看似生效,但 :root 没变,html 标签上没这个属性,CSS 选择器 [data-theme="dark"] 根本不匹配。
- 正确写法:
document.documentElement.setAttribute('data-theme', 'dark') - 初始化时也要写在
上(服务端渲染或首屏直出更稳) - 避免和第三方库的 class 冲突,比如不要混用
class="theme-dark"和data-theme
CSS 变量不能只定义不使用,var() 必须显式调用
CSS 自定义属性本身只是“存值”,不是“开关”。写了 --bg: #fff 却没在任何样式规则里写 background-color: var(--bg),等于白定义。尤其容易漏掉 border、shadow、svg fill 这些非背景/文字类属性。
使用场景:所有可主题化的视觉属性都该走变量,包括但不限于:--border-color、--shadow-sm、--icon-fill、--disabled-opacity。
- 深色模式下,
--text-color和--background-color往往要成对调整,否则出现白字配白底 - 动画过渡要加在使用
var()的属性上,比如transition: background-color 0.2s, color 0.2s - 不要在 SCSS 编译期用
map-get读取 CSS 变量——它运行时才存在,编译时是空的
手动切换和系统偏好必须共存,prefers-color-scheme 只用于首次加载 fallback
prefers-color-scheme: dark 是只读媒体查询,无法被 JS 触发重计算。如果仅靠它做主题,用户点按钮切了暗黑模式,再切回浅色,系统设置没变,页面就卡死在暗黑状态。
正确做法是以 data-theme 为唯一可信状态源,prefers-color-scheme 仅在页面加载时判断一次,作为初始值:
- 加载时读
localStorage.getItem('theme'),有则直接设data-theme - 没有则查
window.matchMedia('(prefers-color-scheme: dark)').matches,设初始值并存入localStorage - 监听系统变化:
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ...),但仅当用户未手动切换过时才响应(可用一个标志位userInitiated控制)
localStorage 同步必须和 DOM 更新严格顺序执行
切换主题后,先改 DOM 属性再存 localStorage,否则极端情况下(如用户快速连点两次),第二次点击读到的还是旧值,导致状态错乱。
性能影响:localStorage 是同步阻塞 API,但主题切换频次极低,无需降级为 setTimeout 或异步写入;重点是顺序不能反。
- 错误顺序:
localStorage.setItem('theme', theme); document.documentElement.setAttribute('data-theme', theme); - 正确顺序:
document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); - 如果用了框架或自定义事件,确保事件回调里也遵守这个顺序
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











