必须把所有变量写在:root里,因为css变量依赖继承传播,只有:root(即html根元素)能确保var(--text-primary)在任意层级(包括::before、svg、contenteditable)正确取值;写在其他选择器中会导致作用域受限、js修改无效或页面“失色”。

直接用 :root 声明变量 + dataset.theme 切换属性,是最稳、最易维护的路径。其他方式要么优先级难控,要么刷新闪屏,要么不兼容 SSR。
为什么必须把所有变量写在 :root 里
CSS 变量靠继承传播,:root 等价于 html 元素,是整个文档树的根。只有挂在这里,var(--text-primary) 才能在任意层级(包括 ::before、svg、contenteditable 区域)正确取值。
- 写在
.theme-dark里:变量只在该 class 子树生效,JS 修改document.documentElement.style完全无效 - 写在组件级选择器如
.card:会覆盖根变量,但兄弟组件读不到,且无法被全局切换逻辑控制 - 没声明就
setProperty:浏览器找不到定义,var(--bg-color)回退为transparent或继承值,整页瞬间“失色”
dataset.theme 切换比 classList 更可靠
用 html[data-theme="dark"] :root 覆盖变量,比 .dark :root 权重更高(110 vs 10),不易被业务 class 覆盖,也避开命名冲突(比如你已有 .dark-mode-toggle)。
- 初始化时必须预设:
<script>document.documentElement.dataset.theme = localStorage.getItem('ui-theme') || 'light';</script>放在里,否则 FOUC - 切换后必须同步
localStorage.setItem('ui-theme', 'dark'),否则刷新变回默认 - 别只改
dataset.theme却忘了在 CSS 里写对应规则:html[data-theme="dark"] { --bg-color: #121212; }
transition 不能写在变量名上,得作用于具体属性
transition: --primary-color 无效,浏览器直接忽略,连警告都不报。真正生效的方式是把变量用在支持过渡的属性上,再对那个属性加 transition。
- ✅ 正确:
background-color: var(--bg-color); transition: background-color 0.3s ease; - ❌ 错误:
transition: --bg-color 0.3s; - 仅对
color、background-color、opacity、transform等原生可插值属性加transition - 所有尺寸/圆角/间距变量也得在初始 CSS 中定义,否则切换时 layout 抖动
contenteditable 元素要显式继承颜色和光标
contenteditable 区域默认不继承 color 和 background,切主题后文字可能还是黑底白字,或光标颜色错乱。
- 必须显式写:
[contenteditable] { color: var(--text-primary); background-color: var(--bg-primary); caret-color: var(--text-primary); } - 伪元素(
::before/::after)、svg fill、input::placeholder同样容易漏,所有用到变量的地方都得显式调用var(--xxx)并加回退值 - 回退值不是可选项:
color: var(--text-primary, #333);,否则变量未定义时会变成黑底白字或透明
最容易被忽略的是:所有主题变量必须在 :root 中完整显式声明,哪怕某套主题里某个变量值和 light 模式相同,也得写出来。漏一个,就有一个地方卡死在旧值上——尤其是边框、阴影、SVG 图标这些非主视觉区域。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











