:root 是定义 css 全局变量的唯一正确位置,因其特异性更高、跨上下文兼容性更好,且在旧 safari、svg、shadow dom 等场景下稳定生效;html 选择器声明变量属误用,不保证全局继承。

直接在 :root 里用 --color-primary 这类双破折号开头的声明,就是定义全局主题色的唯一正确方式;其他位置(比如 html、body 或某个 class)声明的变量,都不算“全局”,子元素大概率读不到或被意外覆盖。
为什么必须用 :root 而不是 html
虽然 :root 在 HTML 中等价于 html 元素,但它语义更准、优先级略高,且在 SVG、Shadow DOM 等非标准 HTML 上下文中也保持一致行为。写成 html { --color-primary: #007bff; } 看似能用,实则是误用——它只是普通元素选择器,不保证跨上下文生效。
-
:root是 CSS 规范定义的“根作用域”,所有后代元素天然继承,无需额外设置inherit - 如果同时写了
html { --x: red; }和:root { --x: blue; },最终生效的是:root的值 - 旧版 Safari(iOS 12.2 之前)对
html声明的变量支持不稳定,而:root更可靠
:root 里声明变量的硬性规则
浏览器对语法极其严格,错一个字符就等于没声明:
- 变量名必须以
--开头,比如--color-primary,写成color-primary或$primary直接被忽略 - 值必须是合法 CSS 值:颜色写
#007bff、rgb(0, 123, 255),尺寸带单位如16px,字符串加引号如"bold" - 避免在
:root内部嵌套引用其他变量,例如--color-text: var(--color-primary);—— 部分旧版 Safari 会解析失败 - 声明块必须出现在实际使用
var(--xxx)的样式规则之前,CSS 是顺序解析的
如何安全地在组件中使用 var(--xxx)
var() 不报错也不警告,但变量未定义时属性会“消失”(回退到 inherit 或 initial),导致文字看不见、背景透明等静默故障:
- 每个
var()必须带 fallback,例如color: var(--text-primary, #333);,第二个参数只在变量未定义/拼错时启用 - 不要指望 fallback 实现主题切换,它只是兜底值;深色模式该用
:root[data-theme="dark"]覆盖整组变量 - 禁止在
@keyframes中直接用var()控制动画值,部分浏览器不支持 - 伪元素
::before/::after和svg的fill/stroke必须显式写var(--color-primary),否则不响应主题变更
JS 动态修改 :root 变量要注意什么
用 document.documentElement.style.setProperty() 确实能实时更新,但有隐藏限制:
- 读取时返回值带空格,比如
getComputedStyle(document.documentElement).getPropertyValue('--primary')返回" #007bff ",记得.trim() - 不能在内联
style属性里声明新变量:<div style="--x:red"> 只对该元素生效,不进入全局作用域 <li>频繁调用 <code>setProperty()(如每帧一次)可能引发样式重计算抖动,批量修改建议先拼好cssText再一次性赋值 - 真正容易被忽略的是:第三方 UI 库(如 Ant Design)若未开启 CSS 变量支持,它的内部 class 仍用硬编码颜色,不会响应你的
--primary-color











