跳到主要内容

CSS 继承与层叠

很多同学学 CSS 时,最容易卡住的不是“属性怎么写”,而是:

  1. 为什么我明明没给子元素写颜色,它却变色了?
  2. 为什么同一个元素命中了好几条规则,最后生效的不是我以为的那条?
  3. 为什么有时是“从父元素继承”,有时又是“后写覆盖前写”?

这背后其实是两个核心机制在配合工作:

  • 继承(Inheritance):当前元素自己没拿到值时,某些属性可以从父元素接过来
  • 层叠(Cascade):当多条声明同时竞争同一个属性时,浏览器按规则选出赢家

你可以先记住一句最重要的话:

继承解决“没人给我值怎么办”,层叠解决“很多人都给我值时听谁的”。


一、先建立一张正确脑图

在 CSS 里,浏览器不是“整块整块”地决定样式,而是针对每个元素的每个属性分别计算。

比如浏览器会分别计算:

  • 这个元素的 color 是多少?
  • 这个元素的 font-size 是多少?
  • 这个元素的 border-color 是多少?
  • 这个元素的 margin-top 是多少?

每个属性的求值,大致都可以归结为两条路:

  1. 当前元素自己有声明:那就进入层叠,比较谁赢
  2. 当前元素自己没有声明:那就看该属性能不能继承;能继承就拿父元素的值,不能继承就回到初始值

所以你要特别注意:

  • 继承和层叠不是对立关系,而是前后衔接的两个阶段
  • 继承不是所有属性都有,只有一部分属性默认可继承
  • 层叠也不是所有时候都发生,只有出现“多个候选声明”时才需要比较
先别急着背规则

学习这篇文档时,建议你脑中一直带着一个问题:

“浏览器现在是在解决没有值的问题,还是在解决多个值冲突的问题?”

一旦把这个问题想清楚,很多 CSS 疑惑都会立刻通顺。


二、什么是继承(Inheritance)

2.1 继承的本质是什么?

继承的意思是:

当某个属性本身支持继承,且子元素没有为这个属性拿到更直接的值时,子元素会使用父元素的最终值。

注意这里有两个关键词:

  1. 属性本身支持继承
  2. 子元素没有更直接的值

也就是说,继承不是“强制复制父元素所有样式”,而是一个带条件的回退机制

2.2 一个最典型的例子:文字颜色

<article class="card">
<h2>CSS 学习笔记</h2>
<p>这段正文没有直接写 color。</p>
<p class="highlight">这段正文自己写了 color。</p>
</article>
.card {
color: #334155;
font-size: 16px;
}

.highlight {
color: #e11d48;
}

浏览器会这样理解:

  • h2 没有自己的 color,而 color 可继承,所以拿 .cardcolor
  • 第一个 p 也没有自己的 color,所以也继承 .cardcolor
  • .highlight 自己写了 color,因此不再使用继承值,而是直接用自己的声明

最终结果是:

  • h2#334155
  • 第一个 p#334155
  • .highlight#e11d48

这说明:继承永远不是最高优先级,它只是“当前元素没有更近值时”的补位方案。

2.3 子元素继承的是“父元素最终值”

这一点非常关键。

子元素继承的,不是父元素某条原始声明,而是父元素经过层叠计算后得到的最终值

.wrapper {
color: #475569;
color: #0f172a;
}

.wrapper span {
/* 没写 color */
}

这里 span 继承到的是 #0f172a,不是 #475569。因为对父元素 .wrapper 来说,真正生效的是后面的那条声明。

这也是为什么我们会说:

  • 先算父元素自己的结果
  • 再决定子元素是否继承这个结果

2.4 哪些属性通常会继承?

可以先记一个规律:

和“文字展示、阅读体验、书写方向”相关的属性,往往更容易继承。

常见可继承属性如下:

类别常见属性
文字颜色color
字体相关font-familyfont-sizefont-stylefont-weight
文本排版line-heighttext-alignletter-spacingword-spacing
列表与光标list-stylecursor
可见性与书写visibilitydirection
自定义属性--brand-color--space-md 这类 CSS 变量

其中要特别记住:CSS 自定义属性(变量)默认也是继承的。

:root {
--brand-color: #2563eb;
}

.card {
color: var(--brand-color);
}

如果子元素没有重新定义 --brand-color,它会一路往上找,直到找到祖先中的定义。

2.5 哪些属性通常不会继承?

同样先记一个规律:

和“盒子大小、位置、边框、背景、布局”相关的属性,大多数默认不继承。

常见不可继承属性如下:

类别常见属性
盒模型widthheightmarginpadding
边框背景borderbackgroundbox-shadow
布局定位displaypositiontopleftz-index
溢出与变换overflowtransform

例如:

.parent {
border: 2px solid #0ea5e9;
background: #e0f2fe;
}

子元素不会因为父元素有边框和背景,就自动也长出同样的边框和背景。

2.6 为什么 div 是块级,不是继承来的?

这是一个非常高频的误区。

很多初学者会以为:

  • 父元素是块级
  • 所以子元素也“继承”成块级

其实不是。

div { display: block; } 这种表现,大多来自浏览器默认样式(User Agent Stylesheet),不是继承。

也就是说:

  • 继承:来自父元素的最终值
  • 浏览器默认样式:来自浏览器内置规则

这两个来源完全不是一回事。


三、手动控制继承:inheritinitialunsetrevert

默认规则之外,CSS 还允许你主动指定一个属性该怎么取值。

3.1 这几个关键字分别是什么意思?

关键字含义适合理解成
inherit强制使用父元素的最终值“不管默认是否继承,我都要继承”
initial使用该属性的规范初始值“回到属性出厂设置”
unset可继承属性表现为 inherit,不可继承属性表现为 initial“恢复默认继承逻辑”
revert回退到当前样式来源之前的结果“把我这一层来源的影响撤掉”
revert-layer回退到当前层叠层之前的结果“把我这一层 @layer 的影响撤掉”

3.2 inherit:让本来不继承的属性也继承

.card {
border: 1px solid #94a3b8;
}

.card strong {
border: inherit;
}

默认情况下 border 不继承,但写了 inherit 之后,strong 就会显式拿到父元素边框。

3.3 unset:最适合“回到默认逻辑”

.panel {
color: #1d4ed8;
}

.panel p {
color: unset;
}

因为 color 是可继承属性,所以这里的 unset 等价于 inherit,最终还是会拿父元素的颜色。

如果换成 border-color: unset;,那它就更接近 initial,因为 border-color 默认不是继承属性。

3.4 revertrevert-layer:进阶但很有用

这两个关键字更适合你已经开始使用:

  • UI 框架
  • 样式重置
  • @layer 分层样式

它们的价值在于:不是简单重置成初始值,而是回退到“更早的层次结果”。

比如你引入了一套第三方组件样式,又在自己的层里做了覆盖;这时如果想临时撤回当前层的影响,revert-layer 会比瞎写 !important 更优雅。

实战建议

如果你还是 CSS 初学阶段,优先熟悉 inheritinitialunset。等你开始接触组件库覆盖、@layer 和大型样式工程化时,再把 revertrevert-layer 用起来。


四、什么是层叠(Cascade)

4.1 层叠到底在解决什么问题?

层叠解决的是:

同一个元素、同一个属性,同时有多条声明可用时,浏览器到底选哪一条。

请注意三个前提:

  1. 必须是同一个元素
  2. 必须是同一个属性
  3. 必须是多条候选声明同时命中

比如:

<p class="desc hot">今天的价格已更新</p>
p {
color: #64748b;
}

.desc {
color: #2563eb;
}

.hot {
color: #dc2626;
}

这三条规则都命中了同一个 p,而且都在设置 color,所以浏览器必须进入层叠比较。

4.2 层叠不是“比较整条规则”,而是“逐属性比较”

这一点很多人会忽略。

.card {
color: #334155;
background: #f8fafc;
}

.warning {
color: #dc2626;
}
<div class="card warning">提示信息</div>

最终结果不是“整条 .warning 赢了,所以 .card 全部失效”,而是:

  • color.warning
  • background:只有 .card 提供,所以仍使用 .card

所以层叠一定要按属性逐个看,不能一把抓。

4.3 继承与层叠的分工

你可以把它们理解成两个连续步骤:

  1. 先看当前元素自己有没有候选声明
  2. 如果有多个候选声明,层叠来决策
  3. 如果一个都没有,再决定要不要继承

五、层叠的判定顺序到底是什么?

5.1 先记住实战版顺序

在日常开发里,最常见的理解顺序可以先记成:

  1. 先看规则是否相关:选择器是否命中、媒体查询是否成立
  2. 再看来源与重要性:比如浏览器默认样式、作者样式、!important
  3. 再看层叠层 @layer
  4. 再看权重(Specificity)
  5. 最后看书写顺序:后写覆盖先写

5.2 第一步:相关性(Relevance)

不是写在 CSS 里的东西都会参与竞争,先得满足“相关”。

例如:

@media print {
.title {
color: black;
}
}

.title {
color: #2563eb;
}

如果当前是在屏幕环境下浏览页面,那么 @media print 里的规则根本不参与本轮竞争。

5.3 第二步:来源与重要性(Origin + Importance)

CSS 值可能来自多个来源:

  • 浏览器默认样式(user agent)
  • 用户自定义样式(user)
  • 开发者写的样式(author)

大多数前端日常开发,主要会碰到这两类:

  • 浏览器默认样式
  • 开发者样式

!important 会把声明提升到“重要声明”队列中,它比较的是优先级阶段,不是权重。

!important 不是权重的一部分

很多同学会说“我给它加了 !important,所以权重更高”。

这个说法不准确。

更准确的说法是:!important 改变了层叠阶段里的“重要性”,让它先于普通声明比较。

5.4 第三步:层叠层 @layer

现代 CSS 新增的 @layer,本质上是在“比权重前”先人为划分样式优先级。

这对大型项目特别有价值,因为它能减少“权重大战”。

@layer reset, base, components, utilities;

@layer base {
.card .title {
color: #334155;
}
}

@layer utilities {
.text-danger {
color: #dc2626;
}
}
<div class="card">
<h3 class="title text-danger">库存不足</h3>
</div>

虽然 .card .title 的选择器看起来更“重”,但如果 utilities 层优先级更高,那么 .text-danger 依然可以赢。

这就是 @layer 的价值:

  • 先按层定大方向
  • 再在层内谈权重
推荐的工程化分层

很多项目会把样式拆成:

  • reset:浏览器默认样式兜底与重置
  • base:全局基础样式
  • components:组件样式
  • utilities:原子类、工具类

这样谁该覆盖谁,会比单纯拼选择器清晰很多。

5.5 第四步:权重(Specificity)

如果前面几轮都还没有分出胜负,浏览器才会比较选择器权重。

这里先记一句就够:

权重比较的是“选择器有多具体”,不是“谁写得更长”。

如果你想把这部分彻底学透,建议继续阅读下一篇:CSS 权重(Specificity)

5.6 第五步:书写顺序(Order of Appearance)

如果:

  • 来源相同
  • 重要性相同
  • 层相同
  • 权重也相同

那么最后就看谁写在后面,后写覆盖先写。

.btn {
color: #2563eb;
}

.btn {
color: #9333ea;
}

最终颜色是 #9333ea

5.7 补充:现代 CSS 里还有 @scope

如果你使用了 @scope,在某些完全打平的情况下,浏览器还会比较“离作用域根有多近”。

不过对大多数前端日常样式编写来说,先把下面这条顺序记牢就够用了:

来源与重要性 → @layer → 权重 → 后写覆盖先写


六、把“继承”和“层叠”放进同一个例子里

下面这段代码,特别适合理解两者怎么配合。

<section class="article">
<h2 class="title">CSS 继承与层叠</h2>
<p>正文第一段</p>
<p class="warning">正文第二段</p>
<button class="action">立即阅读</button>
</section>
.article {
color: #334155;
font-size: 18px;
}

.title {
color: #0f172a;
}

.warning {
color: #dc2626;
}

.action {
border: 1px solid currentColor;
padding: 8px 14px;
}

浏览器会这样算:

6.1 对 h2.title

  • font-size:自己没写,font-size 可继承,所以继承 .article18px
  • color:自己写了 .title { color: #0f172a; },因此不再继承 .articlecolor

6.2 对第一个 p

  • font-size:继承 .article18px
  • color:继承 .article#334155

6.3 对第二个 p.warning

  • font-size:继承 .article18px
  • color:自己有 .warning 声明,所以走层叠,最终是 #dc2626

6.4 对 button.action

  • font-size:通常会继承父级文字环境,表现得和正文协调
  • color:如果按钮没有额外颜色声明,就会继承 .article#334155
  • border:边框本身不继承,但 border: 1px solid currentColor; 使用了 currentColor,因此边框颜色会跟随按钮最终文字颜色

这个例子说明了两个关键点:

  1. 继承主要解决“默认带过去什么”
  2. 层叠主要解决“当前元素自己到底采用哪条声明”

七、实战中最容易混淆的几个点

7.1 * 命中所有元素,不等于“继承”

* {
color: #334155;
}

这不是继承,而是每个元素都被通配符规则直接命中

所以:

  • * 是选择器匹配
  • 继承是没有声明时的回退机制

它们是两回事。

7.2 父元素有背景色,子元素没背景色,不代表子元素继承了背景色

这是视觉上最容易误判的一个点。

很多时候你看到子元素区域也是蓝色,只是因为父元素背景铺在后面,子元素背景是透明的,并不是它“继承了背景色”。

7.3 后代选择器命中子元素,也不是继承

.card p {
color: #2563eb;
}

这条规则不是让 p 去继承 .card,而是直接给 p 指定了值。

7.4 !important 能赢,不代表它“更具体”

!important 是改层叠优先级,不是改权重。

所以说:

  • 更具体:是 Specificity 的话题
  • 更优先:可能是 !important、来源、层、顺序等共同作用

7.5 行内样式很强,但也不是无敌

<p style="color: #2563eb;">Hello</p>

普通行内样式在作者样式里通常非常强,但它仍然可能被更高优先级的情况覆盖,例如:

  • 更高来源的重要声明
  • 过渡中的属性值

所以不要把它理解成“永远最强”。

7.6 CSS 变量的“继承感”很强

.theme-dark {
--text-color: #e2e8f0;
}

.theme-dark .card {
color: var(--text-color);
}

很多设计系统会大量使用这种方式,因为变量天然适合“从外层往里传主题值”。

7.7 子元素继承不到,不一定是“继承失效”

更常见的情况是:

  • 子元素自己有更直接的声明
  • 浏览器默认样式给了它别的值
  • 你用的是不可继承属性

7.8 先问“这个属性默认继承吗”,再问“为什么没继承”

这是排错效率最高的一步。

比如你问:

  • “为什么子元素没继承 margin?”

那答案大概率就是:因为 margin 本来就不继承。


八、推荐一个实用的排错顺序

以后你在 DevTools 里排查样式冲突,可以按下面 5 步走:

第 1 步:先确认属性是否可继承

如果这个属性本来就不继承,那就别在“父元素传给子元素”这条路上浪费时间。

第 2 步:确认当前元素自己有没有被规则直接命中

如果子元素自己被命中了,那通常会优先看当前元素自己的候选声明,而不是直接看父元素。

第 3 步:如果有多条声明竞争,按层叠顺序排查

依次检查:

  1. 来源与 !important
  2. 是否在不同 @layer
  3. 权重谁更高
  4. 谁写在后面

第 4 步:确认是不是浏览器默认样式在起作用

很多 margindisplay、标题字号、列表缩进,都可能来自浏览器默认样式。

第 5 步:必要时查看计算后样式(Computed)

浏览器 DevTools 里的 Computed 面板非常适合查最终结果,因为它展示的是:

  • 这个属性最终是多少
  • 这个值是继承来的、默认的,还是哪条规则赢出来的
一句经验总结

遇到 CSS 样式不生效时,不要只盯着选择器长短。

先问:它有没有值?值从哪来?如果有多个值,谁赢了?


九、工程实践里怎么尽量少打“权重大战”

理解继承与层叠之后,真正高级的做法不是“背更多覆盖技巧”,而是从结构上减少冲突

9.1 优先用分层而不是拼命抬权重

与其写:

#app .page .content .card .title {
color: #dc2626;
}

不如把样式职责分清:

@layer base, components, utilities;

这样你会更容易解释“为什么它该覆盖它”。

9.2 让继承替你传递文字环境

很多文字相关样式,其实没必要层层重写。

例如:

.article {
color: #334155;
line-height: 1.8;
font-family: system-ui, sans-serif;
}

正文内部不少元素都可以自然继承这些值,这样代码会更短,也更统一。

9.3 谨慎使用 !important

!important 能解决问题,但也很容易把后续维护变成“更大的 !important”。

更推荐的顺序通常是:

  1. 先看是不是写错了作用目标
  2. 再看是否应该调整层结构或 @layer
  3. 再看是否需要优化选择器设计
  4. 最后才考虑 !important

十、易错点速记

  1. 继承解决“当前元素没值怎么办”。
  2. 层叠解决“当前元素多个值冲突时听谁的”。
  3. 不是所有属性都继承,文字相关常继承,盒模型相关大多不继承。
  4. 继承拿到的是父元素的最终值,不是随便某条声明。
  5. !important 不是权重的一部分,它属于层叠里的“重要性”。
  6. * 命中所有元素,不等于继承。
  7. 浏览器默认样式和继承不是一回事。
  8. 真正排错时,要按“来源/重要性 → 层 → 权重 → 顺序”来查。

十一、面试高频问答

Q1:CSS 里的继承和层叠分别解决什么问题?

:继承解决的是“当前元素自己没有值时,某些属性能否从父元素拿值”;层叠解决的是“同一个元素的同一个属性被多条声明命中时,最终采用哪一条”。前者偏“缺省传递”,后者偏“冲突决策”。


Q2:为什么 color 经常会继承,而 margin 不会?

:因为 CSS 在设计上就把属性分成了“默认可继承”和“默认不可继承”两大类。文字展示相关属性,如 colorfont-sizeline-height,更适合继承;盒模型和布局相关属性,如 marginpaddingborderwidth,通常不继承,否则布局会变得不可控。


Q3:子元素继承到的是父元素的声明值,还是最终值?

:继承到的是父元素经过层叠计算后的最终值。也就是说,父元素如果先写了 color: red,后面又被别的规则改成了 blue,那么子元素继承到的是 blue


Q4:!important 属于 Specificity 吗?

:不属于。!important 不是权重的一部分,它影响的是层叠算法中的“重要性”阶段。通常是先比较来源与重要性,再比较层,再比较 Specificity,最后才比较书写顺序。


Q5:为什么有时看起来像“继承”,其实不是?

:常见有三种误判:

  1. 用了 * 或后代选择器直接命中了子元素
  2. 父元素背景透出来,让你误以为子元素继承了背景
  3. 浏览器默认样式本来就给该元素设置了值

所以要先区分:这是父元素传下来的,还是子元素自己被规则命中了。


Q6:层叠比较时,为什么有时权重高也没赢?

:因为权重不是第一关。浏览器会先比较来源与重要性,再比较层叠层;只有这些都打平后,才轮到 Specificity。比如普通作者样式即使权重很高,也可能输给更高优先级的 !important 声明,或者输给更高优先级的层。


Q7:@layer 和 Specificity 的关系是什么?

@layer 是在 Specificity 之前的一层优先级控制。它的作用是先规定“哪一组样式更有发言权”,然后再在该组内部比较权重。好处是可以减少为了覆盖样式而不断抬高选择器权重的问题。


Q8:如何一句话解释“为什么这个样式没生效”?

:可以从四个方向回答:

  1. 规则可能没命中元素
  2. 命中了,但别的声明在层叠中赢了
  3. 这个属性本来就不继承
  4. 看到的效果其实来自浏览器默认样式或视觉叠加,而不是继承

如果面试官继续追问,你再按“来源/重要性 → 层 → 权重 → 顺序”的顺序展开即可。