优先级

优先级(Specificity)是浏览器用来决定哪个 CSS 声明与元素最相关,从而决定该元素的属性值的算法。优先级算法通过计算 CSS 选择器的权重,来决定相互竞争的 CSS 声明中哪一个规则会被应用到元素上。

备注: 浏览器是在确定层叠来源和重要性之后才考虑优先级的。换句话说,对于相互竞争的属性声明,只有在对该属性具有优先权的同一个层叠来源和层的选择器之间,优先级才是相关的和可比较的。当具有优先权的层叠层中相互竞争的声明的选择器优先级相等时,作用域邻近度和出现顺序才会变得相关。

如何计算优先级?

优先级是一种算法,用于计算应用于给定 CSS 声明的权重。权重由匹配元素(或伪元素)的选择器中每个权重类别的选择器数量决定。如果有两个或更多的声明为同一元素提供不同的属性值,那么具有最大算法权重的匹配选择器的样式块中的声明值将被应用。

优先级算法基本上是一个由三个类别或权重——ID、CLASS 和 TYPE——组成的三列值,对应三种类型的选择器。该值表示每个权重类别中选择器组件的数量,并写为 ID - CLASS - TYPE。这三列是通过计算匹配元素的选择器中每个选择器权重类别的选择器组件数量来创建的。

选择器权重类别

选择器权重类别按优先级从高到低排列如下

ID 列

仅包括 ID 选择器,例如 #example。对于匹配选择器中的每个 ID,权重值增加 1-0-0。

CLASS 列

包括类选择器,例如 .myClass,属性选择器如 [type="radio"][lang|="fr"],以及伪类,例如 :hover:nth-of-type(3n):required。对于匹配选择器中的每个类、属性选择器或伪类,权重值增加 0-1-0。

TYPE 列

包括类型选择器,例如 ph1td,以及伪元素如 ::before::placeholder 和所有其他使用双冒号表示法的选择器。对于匹配选择器中的每个类型或伪元素,权重值增加 0-0-1。

无值

通用选择器 (*) 和伪类 :where() 及其参数在计算权重时不被计算,所以它们的值是 0-0-0,但它们确实能匹配元素。这些选择器不影响优先级权重值。

组合器,如 +>~" " (空格) 和 ||,可能会使选择器在选择目标上更具体,但它们不会增加任何优先级权重值。

& 嵌套组合器不增加优先级权重,但嵌套规则会。在优先级和功能方面,嵌套与 :is() 伪类非常相似。

与嵌套类似,:is():has() 和否定 (:not()) 伪类本身不增加权重。但是,这些选择器中的参数会增加权重。它们的优先级权重来自于选择器列表中具有最高优先级的选择器参数。同样,对于嵌套选择器,由嵌套选择器组件增加的优先级权重是逗号分隔的嵌套选择器列表中具有最高优先级的选择器。

下文将讨论 :not():is():has() 和 CSS 嵌套的例外情况

匹配的选择器

优先级权重来自于匹配的选择器。以这个包含三个逗号分隔选择器的 CSS 选择器为例

css
[type="password"],
input:focus,
:root #myApp input:required {
  color: blue;
}

上述选择器列表中的 [type="password"] 选择器,其优先级权重为 0-1-0,将 color: blue 声明应用于所有密码输入类型。

所有 input,无论类型如何,在获得焦点时,都会匹配列表中的第二个选择器 input:focus,其优先级权重为 0-1-1;这个权重由 :focus 伪类 (0-1-0) 和 input 类型 (0-0-1) 组成。如果密码输入框有焦点,它将匹配 input:focus,并且 color: blue 样式声明的优先级权重将是 0-1-1。当该密码输入框没有焦点时,优先级权重仍为 0-1-0

一个嵌套在具有 id="myApp" 属性的元素中的 required input 的优先级是 1-2-1,基于一个 ID、两个伪类和一个元素类型。

如果带有 required 的密码输入类型嵌套在一个设置了 id="myApp" 的元素中,无论它是否获得焦点,其优先级权重都将是 1-2-1,基于一个 ID、两个伪类和一个元素类型。为什么在这种情况下优先级权重是 1-2-1 而不是 0-1-10-1-0?因为优先级权重来自于具有最高优先级权重的匹配选择器。权重是通过从左到右比较三列中的值来确定的。

css
[type="password"] {
  /* 0-1-0 */
}
input:focus {
  /* 0-1-1 */
}
:root #myApp input:required {
  /* 1-2-1 */
}

三列比较

一旦确定了相关选择器的优先级值,就会从左到右比较每列中选择器组件的数量。

css
#myElement {
  color: green; /* 1-0-0  - WINS!! */
}
.bodyClass .sectionClass .parentClass [id="myElement"] {
  color: yellow; /* 0-4-0 */
}

第一列是 ID 组件的值,也就是每个选择器中 ID 的数量。比较相互竞争的选择器的 ID 列中的数字。在 ID 列中值较大的选择器获胜,无论其他列中的值是多少。在上面的例子中,尽管黄色选择器总共有更多的组件,但只有第一列的值起作用。

如果相互竞争的选择器在 ID 列中的数字相同,那么就比较下一列,即 CLASS 列,如下所示。

css
#myElement {
  color: yellow; /* 1-0-0 */
}
#myApp [id="myElement"] {
  color: green; /* 1-1-0  - WINS!! */
}

CLASS 列是选择器中类名、属性选择器和伪类的计数。当 ID 列的值相同时,CLASS 列中值较大的选择器获胜,无论 TYPE 列中的值是多少。下面的例子说明了这一点。

css
:root input {
  color: green; /* 0-1-1 - WINS because CLASS column is greater */
}
html body main input {
  color: yellow; /* 0-0-4 */
}

如果相互竞争的选择器在 CLASSID 列中的数字都相同,TYPE 列就变得相关了。TYPE 列是选择器中元素类型和伪元素的数量。当前两列的值相同时,TYPE 列中数字较大的选择器获胜。

如果相互竞争的选择器在所有三列中的值都相同,那么邻近规则就会生效,即最后声明的样式具有优先权。

css
input.myClass {
  color: yellow; /* 0-1-1 */
}
:root input {
  color: green; /* 0-1-1 WINS because it comes later */
}

:is():not():has() 和 CSS 嵌套的例外情况

任意匹配伪类 :is()、关系伪类 :has() 和否定伪类 :not() 在优先级权重计算中被视为伪类。它们本身不会给优先级等式增加任何权重。然而,传递到伪类括号中的选择器参数是优先级算法的一部分;任意匹配和否定伪类在优先级值计算中的权重是其参数的权重

css
p {
  /* 0-0-1 */
}
:is(p) {
  /* 0-0-1 */
}

h2:nth-last-of-type(n + 2) {
  /* 0-1-1 */
}
h2:has(~ h2) {
  /* 0-0-2 */
}

div.outer p {
  /* 0-1-2 */
}
div:not(.inner) p {
  /* 0-1-2 */
}

注意,在上面的 CSS 对中,由 :is():has():not() 伪类提供的优先级权重是选择器参数的值,而不是伪类本身的值。

这三个伪类都接受复杂的选择器列表,即一个由逗号分隔的选择器组成的列表,作为参数。这个特性可以用来增加一个选择器的优先级

css
:is(p, #fakeId) {
  /* 1-0-0 */
}
h1:has(+ h2, > #fakeId) {
  /* 1-0-1 */
}
p:not(#fakeId) {
  /* 1-0-1 */
}
div:not(.inner, #fakeId) p {
  /* 1-0-2 */
}

在上面的 CSS 代码块中,我们在选择器中包含了 #fakeId。这个 #fakeId 为每个段落的优先级权重增加了 1-0-0

当使用 CSS 嵌套创建复杂的选择器列表时,其行为与 :is() 伪类完全相同。

css
p,
#fakeId {
  span {
    /* 1-0-1 */
  }
}

在上面的代码块中,复杂选择器 p, #fakeId 的优先级取自 #fakeIdspan,因此对于 p span#fakeId span 都创建了一个 1-0-1 的优先级。这与 :is(p, #fakeId) span 选择器的优先级是等效的。

通常,你会希望将优先级保持在最低水平,但如果出于特定原因需要增加元素的优先级,这三个伪类可以提供帮助。

css
a:not(#fakeId#fakeId#fakeID) {
  color: blue; /* 3-0-1 */
}

在这个例子中,所有链接都将是蓝色的,除非被一个包含 3 个或更多 ID 的链接声明覆盖,或者匹配 a 的颜色值包含 !important 标志,或者链接有内联样式颜色声明。如果你使用这种技术,请添加注释来解释为什么需要这个技巧。

内联样式

添加到元素上的内联样式(例如,style="font-weight: bold;")总是会覆盖作者样式表中的任何普通样式,因此,可以认为它具有最高的优先级。可以把内联样式看作是具有 1-0-0-0 的优先级权重。

覆盖内联样式的唯一方法是使用 !important

许多 JavaScript 框架和库会添加内联样式。使用 !important 配合一个非常具体的目标选择器,例如使用内联样式的属性选择器,是覆盖这些内联样式的一种方法。

html
<p style="color: purple">…</p>
css
p[style*="purple"] {
  color: rebeccapurple !important;
}

确保在每次使用 important 标志时都包含注释,以便代码维护者理解为什么使用了这个 CSS 反模式。

!important 例外

标记为 important 的 CSS 声明会覆盖同一层叠层和来源中的任何其他声明。虽然从技术上讲,!important 与优先级无关,但它直接与优先级和层叠交互。它颠倒了样式表的层叠顺序。

如果来自相同来源和层叠层的声明发生冲突,并且一个属性值设置了 !important 标志,那么无论优先级如何,都会应用这个 important 声明。当来自相同来源和层叠层且带有 !important 标志的冲突声明应用于同一元素时,具有更高优先级的声明将被应用。

使用 !important 来覆盖优先级被认为是一种不良实践,应避免用于此目的。理解并有效使用优先级和层叠可以消除任何对 !important 标志的需求。

与其使用 !important 来覆盖外部 CSS(来自外部库,如 Bootstrap 或 normalize.css),不如将第三方脚本直接导入到层叠层中。如果你必须在你的 CSS 中使用 !important,请对你的用法进行注释,以便未来的代码维护者知道为什么该声明被标记为 important,并知道不要覆盖它。但绝对不要在编写插件或框架时使用 !important,因为其他开发者需要将它们整合进来,而无法控制。

:where() 例外

优先级调整伪类 :where() 的优先级总是被替换为零,即 0-0-0。它使得 CSS 选择器在目标元素上非常具体,而不会增加任何优先级。

在创建供无法编辑你 CSS 的开发者使用的第三方 CSS 时,创建具有尽可能低优先级的 CSS 被认为是一种良好实践。例如,如果你的主题包含以下 CSS

css
:where(#defaultTheme) a {
  /* 0-0-1 */
  color: red;
}

那么实现该小部件的开发者可以仅使用类型选择器轻松地覆盖链接颜色。

css
footer a {
  /* 0-0-2 */
  color: blue;
}

@scope 块如何影响优先级

将规则集包含在 @scope 块内不会影响其选择器的优先级,无论作用域根和限制中使用了什么选择器。但是,如果你决定显式添加 :scope 伪类,你需要在计算它们的优先级时将其考虑在内。:scope,像所有常规伪类一样,其优先级为 0-1-0。例如

css
@scope (.article-body) {
  /* :scope img has a specificity of 0-1-0 + 0-0-1 = 0-1-1 */
  :scope img {
  }
}

有关更多信息,请参阅@scope 中的优先级

处理优先级难题的技巧

与其使用 !important,不如考虑使用层叠层,并在你的整个 CSS 中使用低权重的优先级,以便样式可以被稍微更具体的规则轻松覆盖。使用语义化 HTML 有助于提供应用样式的锚点。

使选择器更具体,同时增加或不增加优先级

通过在你选择的元素之前指明你正在设置样式的文档部分,规则会变得更具体。根据你添加它的方式,你可以增加一些、很多或不增加优先级,如下所示

html
<main id="myContent">
  <h1>Text</h1>
</main>
css
#myContent h1 {
  color: green; /* 1-0-1 */
}
[id="myContent"] h1 {
  color: yellow; /* 0-1-1 */
}
:where(#myContent) h1 {
  color: blue; /* 0-0-1 */
}

无论顺序如何,标题都将是绿色的,因为该规则是最具体的。

降低 ID 优先级

优先级是基于选择器的形式的。将元素的 id 作为属性选择器而不是 id 选择器包含进来,是使元素更具体而不增加过多优先级的好方法。在前面的例子中,选择器 [id="myContent"] 在确定选择器优先级时算作一个属性选择器,即使它选择的是一个 ID。

如果你需要使一个选择器更具体但又不想增加任何优先级,你也可以将 id 或选择器的任何部分作为参数包含在 :where() 优先级调整伪类中。

通过重复选择器来增加优先级

作为增加优先级的一种特殊情况,你可以重复 CLASSID 列的权重。在一个复合选择器中重复 id、class、伪类或属性选择器,将在覆盖你无法控制的非常具体的选择器时增加优先级。

css
#myId#myId#myId span {
  /* 3-0-1 */
}
.myClass.myClass.myClass span {
  /* 0-3-1 */
}

谨慎使用此方法,如果可以的话尽量不要用。如果使用选择器重复,请务必在你的 CSS 中添加注释。

通过使用 :is():not()(以及 :has()),即使你无法向父元素添加 id,也可以增加优先级

css
:not(#fakeID#fakeId#fakeID) span {
  /* 3-0-1 */
}
:is(#fakeID#fakeId#fakeID, span) {
  /* 3-0-0 */
}

优先于第三方 CSS

利用层叠层是使一组样式优先于另一组样式的标准方法;层叠层可以在不使用优先级的情况下实现这一点!导入到层叠层中的普通(非 important)作者样式比未分层的作者样式具有更低的优先级。

如果样式来自你无法编辑或不了解的样式表,而你需要覆盖样式,一个策略是将你不受控制的样式导入到一个层叠层中。后续声明的层中的样式具有优先权,而未分层的样式优先于来自同一来源的所有分层样式。

当来自不同层的两个选择器匹配同一个元素时,来源和重要性优先;失败的样式表中的选择器的优先级无关紧要。

css
@import "TW.css" layer();
p,
p * {
  font-size: 1rem;
}

在上面的例子中,所有段落文本,包括嵌套内容,都将是 1rem,无论这些段落有多少个匹配 TW 样式表的类名。

避免和覆盖 !important

最好的方法是不使用 !important。上面关于优先级的解释应该有助于避免使用该标志,并在遇到它时完全移除它。

要消除对 !important 的感知需求,你可以执行以下操作之一

  • 增加先前 !important 声明的选择器的优先级,使其大于其他声明
  • 给它相同的优先级,并将其放在它要覆盖的声明之后
  • 降低你试图覆盖的选择器的优先级。

所有这些方法在前面的章节中都有介绍。

如果你无法从作者的样式表中移除 !important 标志,那么覆盖这些 important 样式的唯一解决方案就是使用 !important。创建一个层叠层来覆盖 important 声明是一个很好的解决方案。有两种方法可以做到这一点

方法 1

  1. 创建一个单独的、简短的样式表,只包含专门用于覆盖你无法移除的任何 important 声明的 important 声明。
  2. 在链接到其他样式表之前,使用 layer() 将此样式表作为你的 CSS 中的第一个导入,包括 @import 语句。这是为了确保 important 覆盖作为第一层被导入。
css
@import "importantOverrides.css" layer();

方法 2

  1. 在你的样式表声明的开头,创建一个命名的层叠层,像这样

    css
    @layer importantOverrides;
    
  2. 每次你需要覆盖一个 important 声明时,就在这个命名的层内声明它。只在层内声明 important 规则。

    css
    [id="myElement"] p {
      /* normal styles here */
    }
    @layer importantOverrides {
      [id="myElement"] p {
        /* important style here */
      }
    }
    

层内 important 样式的选择器优先级可以很低,只要它能匹配你试图覆盖的元素即可。普通层应在层外声明,因为分层样式比未分层样式具有更低的优先级。

忽略树的邻近度

一个元素与给定选择器中引用的其他元素的邻近度对优先级没有影响。

css
body h1 {
  color: green;
}

html h1 {
  color: purple;
}

<h1> 元素将是紫色的,因为当声明具有相同的优先级时,最后声明的选择器具有优先权。

直接目标元素 vs. 继承样式

直接目标元素的样式总是优先于继承的样式,无论继承规则的优先级如何。给定以下 CSS 和 HTML

css
#parent {
  color: green;
}

h1 {
  color: purple;
}
html
<html lang="en">
  <body id="parent">
    <h1>Here is a title!</h1>
  </body>
</html>

h1 将是紫色的,因为 h1 选择器专门针对该元素,而绿色是从 #parent 声明中继承的。

示例

在下面的 CSS 中,我们有三个选择器针对 <input> 元素来设置颜色。对于给定的 input,具有优先权的颜色声明的优先级权重是匹配选择器中权重最大的那个

css
#myElement input.myClass {
  color: red;
} /* 1-1-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */
html body main input {
  color: green;
} /* 0-0-4 */

如果以上所有选择器都指向同一个 input,那么这个 input 将是红色的,因为第一个声明在 ID 列中具有最高的值。

最后一个选择器有四个 TYPE 组件。虽然它的整数值最高,但无论包含多少元素和伪元素,即使有 150 个,TYPE 组件也永远不会优先于 CLASS 组件。当列值相等时,会从左到右比较列值。

如果我们将上面示例代码中的 id 选择器转换为属性选择器,那么前两个选择器将具有相同的优先级,如下所示

css
[id="myElement"] input.myClass {
  color: red;
} /* 0-2-1 */
input[type="password"]:required {
  color: blue;
} /* 0-2-1 */

当多个声明具有相等的优先级时,在 CSS 中找到的最后一个声明将应用于该元素。如果两个选择器都匹配同一个 <input>,颜色将是蓝色的。

补充说明

关于优先级需要记住的几件事

  1. 优先级仅在同一元素被同一层叠层或来源中的多个声明定位时适用。优先级仅对具有相同重要性、相同来源和层叠层的声明有意义。如果匹配的选择器在不同的来源中,则由层叠决定哪个声明优先。

  2. 当同一层叠层和来源中的两个选择器具有相同的优先级时,接着会计算作用域邻近度;作用域邻近度最低的规则集获胜。有关更多详细信息和示例,请参阅@scope 冲突如何解决

  3. 如果两个选择器的作用域邻近度也相同,那么源顺序就会起作用。当所有其他条件都相等时,最后一个选择器获胜。

  4. 根据 CSS 规则,直接目标元素将总是优先于元素从其祖先继承的规则。

  5. 文档树中元素的邻近度对优先级没有影响。

规范

规范
选择器 Level 4
# 优先级规则

另见