先记住这个答案
CSS 自定义属性(--*)默认是继承的。在父元素上声明 --color: red,子元素不声明的话就会继承这个值,通过 var(--color) 拿到的就是 red。子元素可以声明同名的自定义属性来覆盖继承值,只影响自己和自己的后代。这和 color、font-size 这些普通可继承属性的行为完全一样。
特殊的是初始值。普通属性的初始值是规范定义好的(比如 color 的初始值是 canvastext),但未注册的自定义属性初始值是一个叫「保证无效值」的特殊标记。把自定义属性设成 initial,它就变成这个标记,var() 引用时会当作未定义来处理。
- 自定义属性默认会继承
- 子元素声明同名属性可以覆盖继承值
initial可使自定义属性无效inherits: false可关闭继承
继承在 DOM 树上一层层传
自定义属性的继承和普通属性走同一套机制:从 :root 开始,沿着 DOM 树逐层向下。中间任何一个节点声明了同名属性,它下面的后代就用新值,不再往上找。
:root { --main-color: #333; }
.parent { --main-color: #555; }
.child { background: var(--main-color, #000); }如果 .child 是 .parent 的后代,--main-color 取到的是 #555,因为 .parent 覆盖了 :root 的值。如果 .child 不在 .parent 下面,取到的是 :root 的 #333。如果连 :root 都没声明,var() 的第二个参数 #000 生效。
这个机制非常适合做主题切换。在 :root 上定义一整套 --theme-* 变量,组件默认使用这些变量。某个区域需要不同主题时,在这个区域的根元素上重新声明同名变量,所有后代自动用新值,不需要改组件代码。
initial 不是空值,是「保证无效值」
很多人以为 --x: initial 会让 --x 变成空字符串或者 0,实际上未注册的自定义属性的初始值是一个特殊标记,叫 guaranteed-invalid value。这个值不是任何合法的 CSS 值,任何引用它的 var() 都会被视为变量未定义,走回退逻辑。
initial 和 unset 的区别CSS.parent { --x: red; }
/* initial:重置为保证无效值,var(--x) 在这里未定义 */
.child-a { --x: initial; color: var(--x, blue); }
/* unset:自定义属性默认继承,所以 unset 等同于 inherit */
.child-b { --x: unset; color: var(--x, blue); }.child-a 用了 initial,--x 变成保证无效值,var(--x, blue) 走回退,color 是 blue。.child-b 用了 unset,因为自定义属性默认继承,unset 等同于 inherit,所以 --x 继承父元素的 red,color 是 red。
这个区别很容易搞混。记住一个判断方法:unset 对可继承属性等同于 inherit,对不可继承属性等同于 initial。自定义属性默认可继承,所以 unset 就是 inherit。
用 @property 控制继承行为
默认情况下自定义属性都会继承,但有时候你希望某个变量只在声明它的元素上有效,不要传给后代。CSS 的 @property 规则可以做到。
@property 用来注册一个自定义属性,给它指定类型、初始值和是否继承。设置 inherits: false 后,这个属性就不再继承了。子元素要用的话必须自己声明。
@property --local-size {
syntax: '<length>';
inherits: false;
initial-value: 16px;
}注册之后,--local-size 的初始值不再是「保证无效值」而是 16px,并且不会继承。没声明的元素取到的是 16px,不是父元素的值。注册还有一个好处:指定了 syntax 之后浏览器知道值的类型,就可以做过渡动画了。
伪元素(::before、::after)也遵循继承机制。它们被当作父元素的子元素,会继承父元素的自定义属性值。用 @property 设了 inherits: false 的属性,伪元素同样拿不到继承值。
容易答错的地方
- 以为自定义属性不继承
- 这是最常见的错误。自定义属性默认就是继承的,不需要额外声明。除非用
@property显式设置inherits: false,否则它一定会沿 DOM 树向下传递。 - 以为
initial和unset效果一样 - 对自定义属性来说,
initial会把值重置为保证无效值(变量相当于未定义),而unset等同于inherit(继承父元素的值)。两者效果完全不同,混用会导致样式出错。
面试官还会怎么问?
怎么阻止子元素继承某个自定义属性?
两种办法。第一种是在子元素上显式声明 --x: initial,让它变成未定义。第二种是用 @property 注册时设置 inherits: false,从根源上关掉继承。第二种更干净,不需要在每个子元素上都写一遍。
伪元素能拿到父元素的自定义属性吗?
可以。::before 和 ::after 在继承关系上属于父元素的子元素,自定义属性照常继承。如果在父元素上声明了 --color: red,伪元素里的 var(--color) 能正常取到 red。
自定义属性名区分大小写吗?
区分。--main-color 和 --Main-color 是两个完全不同的属性,不会互相覆盖。实际项目里建议统一用小写加连字符的命名风格,避免混乱。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。