CSS 特异性(Specificity)是浏览器用来决定当多条规则作用于同一元素时,哪条 CSS 规则生效的算法。理解 CSS 特异性对于编写可维护的样式表、调试布局问题以及避免滥用 !important 至关重要。本综合指南涵盖特异性层级、计算方法、级联层、现代伪类以及经过实战验证的最佳实践。
1. 什么是 CSS 特异性?
特异性是分配给 CSS 选择器的一个权重值,当多条规则匹配同一元素时,它决定哪个声明生效。它不仅仅取决于源码顺序 —— 更具体的选择器始终胜出,无论它在样式表中的位置如何。
看这个两条规则针对同一元素的例子:
<!-- HTML -->
<header id="header" class="site-header">My Website</header>
/* CSS */
#header {
color: blue; /* Specificity: (1,0,0) — wins! */
}
.site-header {
color: red; /* Specificity: (0,1,0) — loses */
}
/* Result: text is blue, because #header (1,0,0) > .site-header (0,1,0) */尽管第二条规则在源码中出现得更晚,但第一条规则胜出,因为 ID 选择器(#header)的特异性高于类选择器(.site-header)。浏览器会为每个选择器计算一个特异性值,并应用具有最高值的声明。
2. 特异性层级
CSS 特异性遵循严格的四级层次结构。从最高到最低优先级:
- 内联样式 —— 直接写在 HTML
style属性中。特异性:(1,0,0,0) - ID 选择器 —— 如
#navbar、#main-content。特异性:(0,1,0,0) - 类选择器、属性选择器、伪类 —— 如
.btn、[type="text"]、:hover、:focus、:nth-child()。特异性:(0,0,1,0) - 元素选择器和伪元素 —— 如
h1、div、::before、::after。特异性:(0,0,0,1)
通配符选择器(*)、组合器(+、~、>、空格)以及否定伪类(:not())本身不增加特异性。但是,:not() 内部的选择器会计算在内。
/* Hierarchy demonstration */
/* Level 4: Element selector — (0,0,1) */
p { color: black; }
/* Level 3: Class selector — (0,1,0) — overrides element */
.text { color: gray; }
/* Level 3: Attribute selector — (0,1,0) — same level as class */
[data-theme="dark"] { color: white; }
/* Level 3: Pseudo-class — (0,1,0) — same level as class */
p:hover { color: blue; }
/* Level 2: ID selector — (1,0,0) — overrides all above */
#intro { color: green; }
/* Level 1: Inline style — (1,0,0,0) — overrides everything */
/* <p id="intro" style="color: red;">This is red</p> */3. 计算特异性 —— (a,b,c) 表示法
特异性通常表示为元组 (a, b, c),其中 a = ID 选择器的数量,b = 类选择器、属性选择器和伪类的数量,c = 元素选择器和伪元素的数量。部分文档使用四部分表示法 (i, a, b, c),其中 i 代表内联样式。
以下示例展示如何逐步计算特异性:
/* Example 1: p.intro */
/* Elements: p = 1 → c = 1 */
/* Classes: .intro = 1 → b = 1 */
/* IDs: none → a = 0 */
/* Specificity: (0, 1, 1) */
/* Example 2: #main .content p */
/* Elements: p = 1 → c = 1 */
/* Classes: .content = 1 → b = 1 */
/* IDs: #main = 1 → a = 1 */
/* Specificity: (1, 1, 1) */
/* Example 3: div#sidebar ul li.active a:hover */
/* Elements: div, ul, li, a = 4 → c = 4 */
/* Classes: .active, :hover = 2 → b = 2 */
/* IDs: #sidebar = 1 → a = 1 */
/* Specificity: (1, 2, 4) */
/* Example 4: body > main#content div.wrapper p.text span */
/* Elements: body, main, div, p, span = 5 → c = 5 */
/* Classes: .wrapper, .text = 2 → b = 2 */
/* IDs: #content = 1 → a = 1 */
/* Specificity: (1, 2, 5) */
/* Example 5: .nav .dropdown .dropdown-item:first-child::after */
/* Elements: none (but ::after = 1) → c = 1 */
/* Classes: .nav, .dropdown, .dropdown-item, :first-child = 4 → b = 4 */
/* IDs: none → a = 0 */
/* Specificity: (0, 4, 1) */比较特异性:从左到右比较。靠前位置的更高数值总是胜出。(1,0,0) 胜过 (0,15,15),因为 1 个 ID 的权重超过任意数量的类或元素。没有"进位"机制 —— 15 个类不等于 1 个 ID。
4. 特异性示例表 —— 15+ 选择器排序
下表将常见的 CSS 选择器从低到高排列:
| # | 选择器 | 特异性 (a,b,c) | 类别 |
|---|---|---|---|
| 1 | * | (0,0,0) | 通配符 |
| 2 | div | (0,0,1) | 元素 |
| 3 | ul li | (0,0,2) | 2 个元素 |
| 4 | ul ol + li | (0,0,3) | 3 个元素 |
| 5 | h1 + p::first-line | (0,0,3) | 2 个元素 + 1 个伪元素 |
| 6 | .btn | (0,1,0) | 1 个类 |
| 7 | li.active | (0,1,1) | 1 个类 + 1 个元素 |
| 8 | [type="text"] | (0,1,0) | 1 个属性 |
| 9 | a:hover | (0,1,1) | 1 个伪类 + 1 个元素 |
| 10 | div.container p.text | (0,2,2) | 2 个类 + 2 个元素 |
| 11 | .nav .nav-item .nav-link | (0,3,0) | 3 个类 |
| 12 | #header | (1,0,0) | 1 个 ID |
| 13 | #header .logo | (1,1,0) | 1 个 ID + 1 个类 |
| 14 | #header nav ul li.active a | (1,1,4) | 1 个 ID + 1 个类 + 4 个元素 |
| 15 | #main #sidebar .widget | (2,1,0) | 2 个 ID + 1 个类 |
| 16 | #a #b #c .x .y .z | (3,3,0) | 3 个 ID + 3 个类 |
5. !important —— 作用、为何避免、何时可接受
!important 声明完全覆盖正常的特异性计算。带有 !important 的规则会击败任何没有它的规则,无论特异性分数如何。
语法:
/* Normal rule */
.button {
background: blue; /* Specificity: (0,1,0) */
}
/* !important overrides everything without !important */
p {
background: red !important; /* Wins over ANY non-!important rule */
}
/* When two rules both have !important, specificity decides */
#main .button {
background: green !important; /* Specificity: (1,1,0) — wins */
}
.button {
background: red !important; /* Specificity: (0,1,0) — loses */
}
/* The !important escalation problem */
.header { color: blue !important; }
/* Now you need this to override: */
#header.header { color: red !important; }
/* And then someone adds this: */
#page #header.header { color: green !important; }
/* This quickly becomes unmaintainable! */为什么要避免 !important:
- 它破坏了自然的级联机制,使调试变得极其困难。
- 它会引发升级战争 —— 一旦使用了
!important,唯一覆盖它的方式是使用另一个!important加上相同或更高的特异性。 - 重构变得危险,因为移除一个
!important可能破坏其他地方的样式。 - 在大规模项目中使样式表变得不可维护。
何时可以使用 !important:
- 工具类:像 Tailwind CSS 这样的框架在工具类上使用
!important以确保它们始终覆盖组件样式(如.hidden { display: none !important; })。 - 覆盖第三方 CSS:当你无法修改供应商样式且仅靠特异性不够时。
- 无障碍覆盖:用于高对比度或大字体的用户样式表可以合理使用
!important。 - 邮件 HTML:某些邮件客户端需要
!important来覆盖其默认样式。
带 !important 的优先级顺序:普通作者样式 → !important 作者样式 → !important 用户样式 → !important 用户代理(浏览器)样式。过渡声明和动画声明在级联中有自己的特殊处理方式。
6. 级联层 —— @layer
CSS 级联层(2022 年引入,所有现代浏览器均支持)为级联增添了一个新维度,位于特异性和源码顺序之间。层让你无需与特异性较量即可控制哪组样式优先。
基本语法:
/* Declare layer order (layers listed first have lowest priority) */
@layer base, components, utilities;
/* Add styles to each layer */
@layer base {
h1 { color: black; font-size: 2rem; }
a { color: blue; text-decoration: underline; }
}
@layer components {
.card h1 { color: navy; } /* Higher priority than base */
.btn { padding: 8px 16px; background: blue; color: white; }
}
@layer utilities {
.text-red { color: red; } /* Highest priority among layers */
.hidden { display: none; }
}
/* Unlayered styles (highest priority for non-!important) */
h1 { font-family: Georgia, serif; }层的顺序很重要:后声明的层具有更高优先级。在上面的例子中,utilities 样式覆盖 components 样式,而后者覆盖 base 样式 —— 与每层内部的特异性无关。
层如何与特异性交互:
- 更高优先级层中的样式始终胜过更低优先级层中的样式,即使低优先级层有更高的特异性。
- 在同一层内,正常的特异性规则适用。
- 未分层的样式在正常(非
!important)样式中具有最高优先级。 - 使用
!important时,顺序反转:较早(低优先级)层中的!important胜过较晚(高优先级)层中的!important。
实际示例:
@layer base, theme, components;
@layer base {
/* Even with #id selector (1,0,0), this loses to a class in 'components' */
#sidebar { background: white; }
}
@layer components {
/* This wins because 'components' layer has higher priority */
.sidebar { background: gray; }
}
/* !important reversal: earlier layer wins with !important */
@layer base {
#sidebar { background: white !important; } /* WINS with !important */
}
@layer components {
.sidebar { background: gray !important; } /* Loses despite later layer */
}7. :where() 和 :is() —— 特异性控制
:where() 和 :is() 伪类接受选择器列表,匹配至少满足其中一个选择器的元素。关键区别在于它们如何影响特异性。
:where() —— 零特异性
:where() 始终贡献零特异性,无论其内部有什么选择器。这使其非常适合编写易于覆盖的基础样式。
/* :where() — zero specificity regardless of arguments */
/* Specificity: (0,0,1) — only the 'a' element counts */
:where(#nav, .menu, header) a {
color: blue;
text-decoration: none;
}
/* This easily overrides the above because (0,1,0) > (0,0,1) */
.custom-link {
color: red; /* Wins! */
}
/* Great for CSS resets that should be easy to override */
:where(h1, h2, h3, h4, h5, h6) {
margin: 0;
font-weight: bold;
}
/* Any class can override the reset */
.light-heading {
font-weight: 300; /* Easily overrides :where() */
}:is() —— 取最高参数的特异性
:is() 取其最高特异性参数的特异性。如果 :is() 内部的某个选择器是 ID,则整个 :is() 获得 ID 级别的特异性。
/* :is() — takes the specificity of the most specific argument */
/* Specificity: (1,0,1) — #nav is the highest argument, plus 'a' */
:is(#nav, .menu, header) a {
color: blue;
}
/* This does NOT override because (0,1,0) < (1,0,1) */
.custom-link {
color: red; /* Loses! */
}
/* :is() for grouping with consistent specificity */
/* Before (repetitive): */
.card h1, .card h2, .card h3 { color: navy; }
/* After (clean): */
.card :is(h1, h2, h3) { color: navy; }
/* Specificity: (0,1,1) — .card + highest arg (h1/h2/h3 = element) */对比示例:
/* Side-by-side comparison */
/* :where() version — Specificity: (0,0,1) */
:where(.alert, .warning, .error) p { color: red; }
/* :is() version — Specificity: (0,1,1) */
:is(.alert, .warning, .error) p { color: red; }
/* A simple class easily overrides :where() but not :is() */
.normal-text { color: black; }
/* ✓ Overrides :where() version — (0,1,0) > (0,0,1) */
/* ✗ Does NOT override :is() version — (0,1,0) < (0,1,1) */何时使用哪个:对重置样式、默认主题和库 CSS 使用 :where(),使消费者可以轻松覆盖。对选择器分组使用 :is(),以保持正常的特异性。
8. :not() 和 :has() —— 否定和关系伪类的特异性
:not() 本身不增加特异性,但其参数贡献完整的特异性。这与 :is() 的行为相同。
:has()("父选择器")也贡献其最高特异性参数的特异性,与 :is() 和 :not() 相同。
示例:
/* :not() specificity examples */
/* div:not(.active) — Specificity: (0,1,1) */
/* :not() itself = 0, but .active inside = (0,1,0), plus div = (0,0,1) */
div:not(.active) {
opacity: 0.5;
}
/* p:not(#hero) — Specificity: (1,0,1) */
/* :not() = 0, but #hero inside = (1,0,0), plus p = (0,0,1) */
p:not(#hero) {
font-size: 1rem;
}
/* :not() with multiple arguments (selector list) */
/* a:not(.btn, .link) — Specificity: (0,1,1) */
/* Takes highest argument: .btn and .link are both (0,1,0) */
a:not(.btn, .link) {
text-decoration: underline;
}
/* :has() specificity examples */
/* article:has(img) — Specificity: (0,0,2) */
/* :has() = 0, img inside = (0,0,1), plus article = (0,0,1) */
article:has(img) {
display: grid;
grid-template-columns: 1fr 2fr;
}
/* .card:has(> .featured) — Specificity: (0,2,0) */
/* :has() = 0, .featured inside = (0,1,0), plus .card = (0,1,0) */
.card:has(> .featured) {
border: 2px solid gold;
}
/* div:has(#special) — Specificity: (1,0,1) */
/* :has() = 0, #special inside = (1,0,0), plus div = (0,0,1) */
div:has(#special) {
background: lightyellow;
}重要提示:一个常见误区是认为 :not() 不增加特异性。实际上,div:not(.active) 的特异性为 (0,1,1) —— :not() 内部的 .active 增加了类级别的特异性。
9. 内联样式 vs 样式表 —— 特异性值 (1,0,0,0)
直接写在 HTML style 属性中的内联样式在四部分表示法中的特异性为 (1,0,0,0)。这意味着它们会覆盖任何基于选择器的规则,无论它包含多少个 ID、类或元素。
示例:
<!-- Inline style beats everything except !important -->
<p id="intro" class="text hero-text"
style="color: red;">
This text is RED
</p>
/* None of these can override the inline style */
p { color: black; } /* (0,0,1) — loses */
.text { color: black; } /* (0,1,0) — loses */
.text.hero-text { color: black; } /* (0,2,0) — loses */
#intro { color: black; } /* (1,0,0) — loses */
#intro.text.hero-text { color: black; } /* (1,2,0) — loses */
/* Only !important can override inline styles */
#intro { color: black !important; } /* Wins! */如何从样式表覆盖内联样式:
- 在样式表规则中使用
!important(通常不推荐)。 - 使用 JavaScript 移除或修改内联样式。
- 使用带
!important的级联层进行受控覆盖。 - 从一开始就避免内联样式 —— 改用类。
为什么要避免内联样式:不使用 !important 就无法覆盖,将关注点混合(HTML 结构与 CSS 表现),无法使用伪类或伪元素,无法使用媒体查询,在大规模项目中难以维护。
10. 常见特异性冲突
在实际项目中,你会频繁遇到特异性冲突。以下是最常见的场景及其解决方案:
覆盖框架样式(Bootstrap、Material UI)
CSS 框架通常使用中等特异性的选择器。当你的自定义样式不生效时,通常是因为框架选择器的特异性更高。
/* Bootstrap button has specificity (0,1,0) */
.btn-primary {
background-color: #0d6efd;
}
/* Your custom style — same specificity, but source order wins */
/* PROBLEM: If Bootstrap CSS loads AFTER yours, it overrides you */
.btn-primary {
background-color: #7c3aed; /* May or may not work! */
}
/* SOLUTION 1: Increase specificity slightly */
.my-theme .btn-primary {
background-color: #7c3aed; /* (0,2,0) — always wins */
}
/* SOLUTION 2: Use :where() in your base + normal class for overrides */
:where(.btn-primary) {
background-color: #0d6efd; /* (0,0,0) — easy to override */
}
.btn-primary {
background-color: #7c3aed; /* (0,1,0) — wins */
}
/* SOLUTION 3: Use @layer */
@layer framework, custom;
@layer framework {
.btn-primary { background-color: #0d6efd; }
}
@layer custom {
.btn-primary { background-color: #7c3aed; } /* Always wins */
}第三方组件 CSS
嵌入式组件(聊天插件、支付表单、社交嵌入)通常注入高特异性的 CSS。覆盖策略:
/* Third-party widget uses highly specific selectors */
/* e.g., #chat-widget .chat-container .message-bubble { color: black; } */
/* Specificity: (1,2,0) */
/* Strategy 1: Match or exceed their specificity */
#chat-widget .chat-container .message-bubble {
color: white; /* (1,2,0) — same specificity, your CSS loads later */
}
/* Strategy 2: Use !important as last resort */
.message-bubble {
color: white !important; /* Overrides non-!important rules */
}
/* Strategy 3: Shadow DOM encapsulation (for your own widgets) */
/* Styles inside shadow DOM don't compete with outer specificity */
/* Strategy 4: @layer the third-party CSS */
@layer vendor, custom;
@layer vendor {
@import url('third-party-widget.css');
}
@layer custom {
.message-bubble { color: white; } /* Layer priority wins */
}CSS-in-JS 中的特异性
styled-components 和 Emotion 等库会生成特异性为 (0,1,0) 的唯一类名。当它们与全局样式冲突时:
/* styled-components generates: .sc-abc123 { color: blue; } */
/* Specificity: (0,1,0) */
/* Global style that might conflict */
p { color: black; } /* (0,0,1) — styled-component wins */
/* But if global CSS uses a class: */
.dark-theme p { color: white; } /* (0,1,1) — beats styled-component */
/* Solution: Use && to double specificity in styled-components */
const Button = styled.button`
&& {
color: blue; /* Generates .sc-abc123.sc-abc123 → (0,2,0) */
}
`;
/* Solution: Use @layer for global styles */
@layer globals;
@layer globals {
.dark-theme p { color: white; }
}
/* styled-components (unlayered) will win over layered globals */深色模式切换冲突
深色模式实现在主题选择器与组件选择器竞争时经常产生特异性问题:
/* Common dark mode specificity issue */
.card { background: white; color: #333; } /* (0,1,0) */
/* Dark mode with attribute selector */
[data-theme="dark"] .card {
background: #1a1a2e; color: #e0e0e0; /* (0,2,0) */
}
/* Now a modifier class cannot override dark mode! */
.card.highlighted {
background: yellow; /* (0,2,0) — TIES with dark mode */
/* Winner depends on source order — fragile! */
}
/* SOLUTION: Use consistent nesting depth */
[data-theme="dark"] .card.highlighted {
background: #ffd700; /* (0,3,0) — reliably wins */
}
/* BETTER SOLUTION: Use CSS custom properties */
:root {
--card-bg: white;
--card-color: #333;
}
[data-theme="dark"] {
--card-bg: #1a1a2e;
--card-color: #e0e0e0;
}
.card {
background: var(--card-bg);
color: var(--card-color);
}
.card.highlighted {
--card-bg: yellow; /* Works in both themes! */
}
[data-theme="dark"] .card.highlighted {
--card-bg: #ffd700;
}11. 管理特异性的最佳实践
遵循这些经过验证的策略,使任何规模的项目都能保持可管理的特异性:
BEM 方法论
BEM(块、元素、修饰符)通过使用单类选择器保持特异性扁平。每个选择器的特异性都是 (0,1,0)。
/* BEM: flat specificity — every selector is (0,1,0) */
/* Block */
.card { padding: 16px; border: 1px solid #ddd; }
/* Element (block__element) */
.card__title { font-size: 1.5rem; font-weight: bold; }
.card__body { padding: 8px 0; }
.card__footer { border-top: 1px solid #eee; }
/* Modifier (block--modifier) */
.card--featured { border-color: gold; }
.card--compact { padding: 8px; }
/* Element + Modifier */
.card__title--large { font-size: 2rem; }
/* All selectors have specificity (0,1,0) — no conflicts!
Override order is controlled purely by source order. */避免深层嵌套
深层嵌套的选择器会产生高特异性和脆弱的样式。将选择器深度控制在最多 3 层。
/* BAD: Deep nesting creates high specificity */
/* Specificity: (0,0,6) — hard to override */
header nav ul li a span {
color: blue;
}
/* BAD: Nesting with classes makes it worse */
/* Specificity: (1,3,2) — very hard to override */
#page .header .nav-container ul.menu li.active a {
color: red;
}
/* GOOD: Flat, single-class selectors */
/* Specificity: (0,1,0) — easy to override */
.nav-link {
color: blue;
}
.nav-link--active {
color: red;
}
/* ACCEPTABLE: Maximum 3 levels when truly needed */
/* Specificity: (0,2,0) */
.nav .nav-link {
color: blue;
}工具优先方法
工具优先 CSS(Tailwind、UnoCSS)通过直接在 HTML 中应用单一用途的类来完全避免特异性问题。每个工具类的特异性都是 (0,1,0),顺序由样式表生成顺序控制。
<!-- Utility-first: all specificity is (0,1,0) -->
<div class="flex items-center gap-4 p-4 bg-white rounded-lg shadow-md">
<img class="w-12 h-12 rounded-full" src="avatar.jpg" alt="Avatar" />
<div>
<h3 class="text-lg font-semibold text-gray-900">Jane Doe</h3>
<p class="text-sm text-gray-500">Software Engineer</p>
</div>
</div>
<!-- No specificity conflicts because:
1. Each utility = one class = (0,1,0)
2. Order is determined by the stylesheet, not HTML
3. No nesting, no IDs, no !important needed -->
<!-- Tailwind important mode (adds !important to all utilities) -->
<!-- tailwind.config.js: { important: true } -->
<!-- .text-red-500 { color: #ef4444 !important; } -->使用级联层
将 CSS 组织到层中,创建可预测的覆盖行为而无需增加特异性:
/* Recommended layer structure for large projects */
@layer reset, base, tokens, components, layouts, utilities, overrides;
@layer reset {
/* CSS reset / normalize — :where() for zero specificity */
:where(*, *::before, *::after) { box-sizing: border-box; margin: 0; }
:where(body) { line-height: 1.5; }
}
@layer base {
/* Base element styles */
:where(h1) { font-size: 2.5rem; }
:where(a) { color: var(--color-link); }
}
@layer tokens {
/* Design tokens / CSS variables */
:root { --color-primary: #3b82f6; --color-link: #2563eb; }
[data-theme="dark"] { --color-primary: #60a5fa; --color-link: #93c5fd; }
}
@layer components {
/* Component styles */
.btn { padding: 8px 16px; background: var(--color-primary); }
.card { padding: 16px; border-radius: 8px; }
}
@layer utilities {
/* Utility overrides */
.text-center { text-align: center; }
.hidden { display: none; }
}
@layer overrides {
/* Third-party overrides, edge cases */
}特异性审查清单
- 避免在样式表中使用 ID 选择器 —— 改用类。
- 将选择器深度控制在 3 层或更少。
- 除非在工具类中或用于覆盖第三方样式,否则不要使用
!important。 - 对基础/重置样式使用
:where()。 - 对大型项目使用级联层(
@layer)。 - 优先使用单类选择器(BEM 模式)。
- 使用浏览器开发者工具的特异性指示器调试冲突。
12. 常见问题
CSS 特异性是如何计算的?
CSS 特异性被计算为元组 (a, b, c):计算 ID 选择器的数量 (a)、类选择器、属性选择器和伪类的数量 (b),以及元素选择器和伪元素的数量 (c)。从左到右比较 —— 靠前位置的更高数值总是胜出。例如,一个 ID 选择器 (1,0,0) 击败任意数量的类选择器 (0,n,0)。
!important 会覆盖特异性吗?
是的,!important 完全覆盖正常的特异性。带有 !important 的规则会击败任何没有它的规则,无论选择器特异性如何。但是,当两条规则都有 !important 时,它们之间适用正常的特异性比较。级联层增加了另一个维度:较早层中的 !important 胜过较晚层中的 !important。
:where() 和 :is() 的特异性是什么?
:where() 始终具有零特异性,与其参数无关。:is() 取其最高特异性参数的特异性。例如,:where(#id, .class) 的特异性为 (0,0,0),而 :is(#id, .class) 的特异性为 (1,0,0),因为 #id 参数在其参数中具有最高特异性。
256 个类可以覆盖一个 ID 选择器吗?
不能。这是一个常见误区。在所有现代浏览器中,特异性类别之间不会溢出。任何数量的类选择器都无法覆盖单个 ID 选择器。(a,b,c) 值是从左到右独立比较的,而非作为单个连接数字。旧浏览器(IE6 时代)曾有 256 个类可以溢出的 bug,但自约 2012 年以来已不再如此。
级联层如何影响特异性?
级联层(@layer)引入了一个位于级联中特异性之上的优先级系统。更高优先级层中的样式始终胜过更低优先级层中的样式,无论特异性如何。在同一层内,正常的特异性规则适用。未分层的样式胜过所有分层样式。对于 !important 规则,顺序反转:较早的层胜过较晚的层。
掌握 CSS 特异性是编写整洁、可预测样式表的基础。使用扁平选择器、避免在 CSS 中使用 ID、利用级联层和 :where() 控制覆盖、将 !important 留给真正的边缘情况。有了这些原则,特异性冲突将成为过去。