CSS变量多级回退唯一合法方式是嵌套var(),如var(--a, var(--b, #000)),因回退值必须为合法CSS值,而var(--b, #000)可求值,--b单独出现则非法;空字符串、initial等已定义状态不触发回退,仅未声明或unset时生效。

var()嵌套是唯一合法的多级回退方式
浏览器原生只支持一层回退,但var()的第二个参数允许传入另一个var()表达式,从而形成链式降级。这不是 hack,是 CSS 规范明确允许的语法。
关键点在于:回退值必须是合法 CSS 值,而var(--b, #000)本身就是一个可求值的表达式,不是变量名;写成var(--a, --b)则非法——因为--b只是属性名,不是值。
- ✅ 正确:
color: var(--text-color, var(--theme-color, #333)); - ✅ 正确:
background: var(--card-bg, var(--surface-bg, var(--fallback-bg, #fff))); - ❌ 错误:
color: var(--text-color, --theme-color);(第二参数不是值) - ❌ 错误:
color: var(--text-color, var(--theme-color));(内层var()缺回退值,语法不完整)
回退链只在“变量未定义”时触发,空字符串不算
很多人以为把变量设为空就能触发下一级回退,实际不会。只要--x: ""、--x: initial或--x: inherit出现在任何作用域中,该变量就被视为“已定义”,var(--x, ...)会直接取这个值,根本不会往下走。
典型崩坏场景::root { --bg: ""; } + .box { background: var(--bg, var(--fallback, #fff)); } → 渲染为透明背景,不是白色。
立即学习“前端免费学习笔记(深入)”;
- 验证是否真未定义:用
getComputedStyle(el).getPropertyValue('--bg')检查,返回""说明已定义;只有返回""且该变量在所有作用域都未出现过,才是“真未定义” - 想清空变量?删掉那行声明,或显式设为
unset(注意unset会继承,不是空白) - 组件内覆盖变量时,务必赋有效值,比如
--btn-bg: #007bff;,别留空
作用域独立,嵌套里的变量不自动向上查找
var(--a, var(--b, #000))中的--b是在当前选择器作用域内查找的,不是去:root或父元素里找。如果当前作用域没定义--b,才会启用它的回退值。
这意味着:你不能指望“全局 fallback 变量”自动生效,除非把它统一提至:root,或确保每个使用点的作用域里都定义了它。
- ✅ 安全做法:
:root { --fallback-bg: #fff; },然后各处用var(--bg, var(--fallback-bg, #fff)) - ⚠️ 危险做法:
.card { --a: red; background: var(--a, var(--b, #fff)); }→ 若.card里没定义--b,就回退到#fff;但如果:root里有--b: green,它也不会被用到 - 命名建议:组件级变量加前缀,如
--btn-bg,避免和全局--bg冲突
嵌套层级不宜过深,最内层必须是字面量
三层嵌套var(--a, var(--b, var(--c, #000)))浏览器能解析,但再深就难调试,且易引入循环引用风险。
循环引用比如::root { --a: var(--b, red); --b: var(--a, blue); } → 整个声明会被浏览器忽略,Chrome 会报CSS Parser: cycle detected in variable reference。
- 推荐最多两层:外层组件变量 → 内层主题变量 → 最终字面量回退(如
#000、1rem) - 回退值里不要放
calc()或函数式表达式,部分旧版 Safari 对clamp()或rgb(var(--r), ...)支持不稳定 - 回退值中禁止逗号分隔多个值(如
1px solid, red),会被截断


















