:root是唯一可靠位置,因其作用域覆盖整个文档树,确保所有元素可继承;写在body或class内仅局部生效,跨iframe、shadow dom即失效。

直接在 :root 里定义语义化变量(如 --color-primary),并确保所有用色处都显式调用 var(--color-primary),才能真正实现“改一处、全站变”。没提前替换完,就只是幻觉。
为什么:root是唯一可靠的位置
浏览器把 :root 当作 html 元素的别名,它的作用域覆盖整个文档树。写在 body、.app 或某个组件 class 里的变量,只对后代生效——跨页面、跨 iframe、跨 Shadow DOM 就失效了。
- 错误写法:
.theme-light { --color-primary: #3b82f6; }→ 只有该 class 内部元素能取到 - 正确写法:
:root { --color-primary: #3b82f6; }→ 所有元素默认可继承 - 注意:变量名严格区分大小写,
--Color-Primary和--color-primary是两个变量 - 不要在
@media块里单独定义整套变量,部分 Safari 版本会忽略媒体查询中的变量重定义
var() 必须带 fallback,否则容易静默失效
var(--color-primary) 不校验变量是否存在。如果 --color-primary 没定义,它不会报错,而是回退到 CSS 属性默认值——color 变成 inherit,background-color 变成 transparent,页面可能突然不可读。
- 安全写法:
color: var(--color-text, #1f2937); - fallback 必须是字面量值(十六进制、rgb、关键词等),不能是另一个
var() - 嵌套写法
var(--a, var(--b))无效,第二个var()会被当作文本字符串处理 - 调试时打开 DevTools 的 “Computed” 面板,看最终解析出的值,比猜更准
主题切换必须靠 class 或 data 属性,不是 media query
依赖 @media (prefers-color-scheme: dark) 直接覆盖 :root 变量,在旧版 Safari 和部分安卓 WebView 中行为不一致。更稳妥的方式是把主题挂到 html 元素上:
:root {
--color-primary: #3b82f6;
--color-bg: #ffffff;
}
html[data-theme="dark"] {
--color-primary: #60a5fa;
--color-bg: #111827;
}
- JS 切换主题只需:
document.documentElement.setAttribute('data-theme', 'dark') - 避免用
!important覆盖变量,它会破坏级联逻辑,导致后续 JSsetProperty失效 - 第三方库(如 Ant Design)若未启用 CSS 变量支持,需额外加覆盖规则,不能指望它自动响应
最容易漏掉的 var() 替换位置
主题色不只出现在 .btn 或 .header 这类显眼选择器里。漏一处,那一块就卡在旧主题里:
-
::before/::after里的background和color—— 下划线、图标装饰、状态标记常在这里硬编码 - 内联 SVG 的
fill和stroke,必须显式写fill: var(--color-primary);,不能靠继承 - 表单控件状态:
input:focus的outline-color、button:disabled的background-color - Vue/React 的 scoped style 或 Shadow DOM 内,变量不会自动透传,需手动继承或用
:host设置
真正难的不是写几行 :root,而是在每次新增样式时,下意识判断“这里颜色是否该走变量”,以及团队能否长期守住这条线——漏掉一个伪元素,就等于给主题系统开了个后门。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











