必须将所有css变量定义在:root中,因其等价于html根元素,确保全页面任意层级(含::before、svg、contenteditable)均可通过var()继承;写在类选择器或组件内会导致作用域受限、继承断裂或覆盖混乱。

直接用 :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或继承值,整页瞬间“失色”
data-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 里写对应规则::root[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,切主题后文字可能还是黑底白字,或光标颜色错乱。
- 需在 CSS 中显式设置:
[contenteditable] { color: var(--text-primary); background-color: var(--bg-surface); cursor: text; } - 如果用了
scoped样式(如 Vue SFC),注意该规则必须能命中contenteditable元素本身,而非仅其父容器 - 部分旧版 Safari 对
contenteditable的变量继承支持不稳定,建议加 fallback:color: var(--text-primary, #333);
真正麻烦的不是写几行 setProperty,而是变量作用域层级、JS 缓存行为、以及 CSS 选择器权重之间那点微妙的冲突——调不通的时候,先查 computed styles 面板里变量最终值是多少,再看它是从哪条规则来的。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











