必须将所有主题变量定义在 :root 中,因为只有 :root(html 元素)能确保全页面继承;写在 .dark、body 等选择器内会导致兄弟节点、表单控件等无法读取变量。

必须把所有主题变量定义在 :root 里,否则切换必然失效——这不是可选项,是 CSS 变量作用域的硬性限制。
为什么 :root 是唯一安全的位置
CSS 自定义属性靠继承传播,而只有 :root(即 元素)能保证全页面任意元素都能通过 var(--bg-color) 读取到值。写在 .dark、body 或组件类里,变量只在该选择器匹配的子树生效,兄弟节点、表单控件、伪元素都拿不到。
常见错误包括:
-
.theme-dark { --bg-color: #121212; }—— 这个变量只对.theme-dark自身及其后代有效,button或input若没被包裹,就 fallback 到初始值或透明 -
body { --text-color: #eee; }——body不是根,html才是;var(--text-color)在html上求值时根本找不到定义
:root 里怎么写变量才不踩坑
变量名要语义化,值要带单位(颜色、长度、阴影等),且所有主题下用到的变量必须在默认 :root 块中完整声明——哪怕深色模式下值相同,也不能漏。
示例(正确):
:root {
--bg-primary: #ffffff;
--text-primary: #1a1a1a;
--border-color: #e0e0e0;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
}
错误写法:
- 只在
[data-theme="dark"] :root里声明变量,但默认:root没定义--shadow-sm→ 页面一渲染就 fallback 成transparent,按钮阴影消失 - 用
rem或em写变量值却没设好根字体大小,导致响应式错乱 - 变量名含连字符但没加引号,如
--font-size-lg合法,但--font size lg直接无效
手动切换时别直接改 style.setProperty()
用 JS 动态调 document.documentElement.style.setProperty('--bg-primary', '#121212') 看似简单,实际埋了三个雷:
- 它会覆盖
@media (prefers-color-scheme: dark)的自动逻辑,系统切回浅色时变量不会自动还原 - 大量变量逐个 set 会阻塞主线程,尤其在低端安卓机上明显卡顿
- 如果该变量在
:root中未预先声明,浏览器无处 fallback,var(--bg-primary)渲染为inherit或transparent,整页变白/变黑
正确做法是:用 dataset.theme 控制属性,配合 [data-theme="dark"] :root 的 CSS 块批量覆盖。权重稳定、可 SSR、无闪屏。
兼容性与初始化最容易被忽略的点
用户第一次访问时,JS 还没执行,浏览器已按默认 CSS 渲染了一帧——这就是 FOUC(Flash of Unstyled Content)的根源。
必须在 里内联一段极简脚本:
<script>
document.documentElement.dataset.theme = localStorage.getItem('ui-theme') || 'light';
</script>
而不是等 DOM 加载完再操作。同时,每次切换主题时,两件事必须同步:
document.documentElement.dataset.theme = 'dark'localStorage.setItem('ui-theme', 'dark')
漏掉任一,刷新后主题就断连。别指望 CSS 自己记住状态。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











