必须在 :root 中声明 CSS 变量,因其是全局继承唯一起点;写在其他选择器中会导致 JS 无法生效、兄弟组件不可见或被覆盖,且 Safari/Firefox/旧 Chrome 会静默失效。

必须在 :root 中声明,且所有主题切换逻辑必须基于 data-theme 属性或 @media (prefers-color-scheme),否则 Safari、Firefox 和旧版 Chrome 会静默失效或漏继承。
为什么只认 :root,其他地方都不行
浏览器只把 :root 当作全局继承起点——var(--color-primary) 能在 ::before、svg、input::placeholder 里取到值,全靠它。写在 .theme-dark 或 body 里,JS 调用 document.documentElement.style.setProperty() 就完全没反应;写在组件选择器里(比如 .button { --color: red; }),兄弟组件根本读不到,还容易被覆盖。
常见错误包括:
- 把变量塞进
.dark类里,结果默认主题没定义,页面一加载就是空白色或继承色 - 在
@media块外直接覆盖:root,部分旧版 Safari 支持不稳定 - 用
html或body替代:root,特异性低,易被第三方库样式压掉
var() 调用时单位和拼写必须严格匹配
变量值里带单位(如 --space-md: 16px;),调用时就不能再加单位;无单位值(如 --line-height-base: 1.5;)则不能补 px/em。拼错变量名会静默 fallback 到 inherit 或初始值,很难排查。
立即学习“前端免费学习笔记(深入)”;
✅ 正确:padding: var(--space-md) var(--space-lg);
❌ 错误:font-size: var(--font-size-base)px;(重复单位)
❌ 错误:color: var(--color-prime);(拼写错误,无报错但无效)
Vue/React 动态注入时要注意:style={{ '--space-md': 16 }} 必须手动加单位,写成 '16px',否则计算值为 16(无单位),在 padding 等属性中会被忽略。
主题切换别只改变量,得同步控制作用域类名
单纯用 document.documentElement.style.setProperty('--bg', '#1a1a1a') 能生效,但无法触发 CSS 中基于类名的条件覆盖(比如深色模式下图标反色、禁用态透明度调整)。真正可靠的方案是切 class 或 data-theme,让 CSS 规则批量接管。
推荐写法:
- 定义两套变量:
:root { --bg: #fff; }和:root[data-theme="dark"] { --bg: #1a1a1a; } - 切换时只改一个属性:
document.documentElement.setAttribute('data-theme', 'light'),零次setProperty调用 - 避免多次
setProperty批量修改——会导致重排重绘,在低端 Android WebView 里闪动明显
需要 JS 读取当前值时,用 getComputedStyle(document.documentElement).getPropertyValue('--bg'),别缓存旧值。
Safari 15.4 以下 @keyframes 不支持 var()?绕过限制
Safari 直到 15.4 才允许在 @keyframes 中使用 var(),低版本会静默忽略整条声明。这不是 bug,是规范早期实现差异——动画关键帧属于“计算前阶段”,而 CSS 变量是“计算后阶段”才解析的。
实操建议:
- 动画中需要动态颜色/尺寸时,改用
element.style.setProperty('--anim-color', value)在 JS 中实时更新,再让 CSS 读取 - 若必须纯 CSS,把动画拆成多个预设主题类,例如
.theme-dark .fade-in和.theme-light .fade-in,分别写死数值 -
@supports (animation: var(--fake))做特性检测不可靠,Safari 会误判;稳妥做法是 UA 检测 +classList.toggle
最常被忽略的是:SVG 图标内联时,fill 和 stroke 必须显式写 var(--color-primary),否则即使 :root 定义了也继承不到——因为 SVG 元素不自动继承 CSS 自定义属性,除非你手动指定。


















