搞懂 CSS 伪类和伪元素及其区别
Category(分类): CSS, HTML Status: 未知
什么是伪类和伪元素?
- 伪类(Pseudo-class):通常以单冒号
:开头,用于选择处于某种状态、满足某种条件或位于特定结构位置的元素; - 伪元素(Pseudo-element):现代 CSS 通常以双冒号
::开头,用于选择元素的某个特定部分,或生成不对应于 HTML 节点的渲染内容。
伪类和伪元素都属于 CSS 选择器能力,不会自动在 HTML 源码中增加真实的 class 或 element。
伪类示例
下面的选择器表示:当一个链接已经访问过,并且鼠标当前悬浮在它上面时,将链接文字显示为红色:
a:visited:hover {
color: red;
}

无论是 :visited 还是 :hover,它们描述的都是 <a> 元素的状态。选择器最终选中的仍然是链接元素本身,只是在元素满足相应状态时样式才会生效。
需要注意:
- 浏览器会限制
:visited可以修改和读取的样式,主要是为了防止网页探测用户历史记录; :visited通常只适用于带有有效href的链接;- 无痕模式不会在会话结束后保存历史,但当前会话中的访问状态和具体浏览器行为可能不同,不能简单断言“无痕模式绝对没有
:visited”; - DevTools 的状态模拟功能主要适合模拟
:hover、:focus等状态,不能把模拟结果当成真实历史记录。
伪元素示例
下面的语法用于设置元素首字母或首个文字片段的样式:
div::first-letter {
color: red;
}

与伪类不同,::first-letter 不是在描述 div 的状态,而是在选择 div 内容中的一个特殊部分。它不是 HTML 中独立存在的真实节点,而是浏览器根据布局规则暴露出来的伪元素。
::first-letter 通常作用于块容器、列表项、表格单元格或 inline-block 等适合形成首字母区域的盒子。普通 inline 元素通常不能直接应用这一伪元素;可以将其改为 block 或 inline-block,但仍应结合实际布局测试。
从上面的案例可以得出:
- 伪类用于根据元素的状态、结构位置或其他条件选择已经存在的元素;
- 伪元素用于选择元素的某个特殊部分,或生成不对应于 HTML 节点的内容;
- 伪类的优先级通常按 class 选择器计算;
- 伪元素的优先级通常按类型选择器计算。
“伪类的本质是 class、伪元素的本质是 element”只能作为入门时的类比,不能理解为 DOM 中真的增加了 class 或元素。
伪类有哪些?
按照用途,可以大致分为以下几类:
- 用户交互伪类:
:active、:focus、:focus-visible、:hover等; - 链接伪类:
:link、:visited等; - 结构伪类:
:first-child、:nth-child()、:first-of-type等; - 表单状态伪类:
:enabled、:disabled、:required、:valid、:invalid等; - 文档和语言伪类:
:target、:root、:lang()、:dir()等; - 逻辑伪类:
:not()、:is()、:where()、:has()等; - 媒体和显示状态伪类:
:fullscreen、:modal等。
下面列出一些常用伪类。兼容性会随着浏览器版本变化,表格中的描述不代替针对目标浏览器的实际测试。
| 伪类 | 描述 | 当前支持概览 |
|---|---|---|
:active | 元素处于激活状态时 | 广泛支持 |
:focus | 元素获得焦点时 | 广泛支持 |
:focus-visible | 浏览器判断当前焦点应以可见方式提示时 | 现代浏览器广泛支持 |
:hover | 指针悬浮在元素上时 | 广泛支持,触摸设备行为不同 |
:link | 链接尚未访问时 | 广泛支持 |
:visited | 链接已经访问时 | 广泛支持,但可设置样式受隐私限制 |
:first-child | 元素是父元素的第一个子元素时 | 广泛支持 |
:last-child | 元素是父元素的最后一个子元素时 | 广泛支持 |
:nth-child() | 元素匹配父元素子节点序号时 | 广泛支持 |
:nth-last-child() | 从末尾开始匹配子节点序号时 | 广泛支持 |
:only-child | 元素是父元素唯一子元素时 | 广泛支持 |
:first-of-type | 元素是同类型兄弟元素中的第一个时 | 广泛支持 |
:last-of-type | 元素是同类型兄弟元素中的最后一个时 | 广泛支持 |
:nth-of-type() | 元素匹配同类型兄弟元素序号时 | 广泛支持 |
:nth-last-of-type() | 从末尾开始匹配同类型兄弟元素序号时 | 广泛支持 |
:only-of-type | 元素是同类型兄弟元素中唯一一个时 | 广泛支持 |
:not(selector) | 元素不匹配指定选择器时 | 广泛支持 |
:is() | 匹配参数选择器列表中的任意一个 | 现代浏览器广泛支持 |
:where() | 匹配参数选择器列表中的任意一个,优先级为 0 | 现代浏览器广泛支持 |
:has() | 根据后代或相关元素是否匹配来选择当前元素 | 现代浏览器广泛支持,旧浏览器需验证 |
:target | 元素的 ID 匹配 URL 片段时 | 广泛支持 |
:root | 元素是文档树根元素时 | 广泛支持 |
:lang() | 元素或祖先匹配指定语言时 | 广泛支持 |
:dir() | 元素匹配指定书写方向时 | 现代浏览器支持较好,旧浏览器需验证 |
:empty | 元素没有子节点时 | 广泛支持,空白文本节点也会影响匹配 |
:valid / :invalid | 表单控件是否通过约束验证时 | 广泛支持 |
:required / :optional | 表单控件是否必填时 | 广泛支持 |
:in-range / :out-of-range | 数值表单控件是否处于范围内时 | 广泛支持 |
:read-only / :read-write | 元素是否处于只读或可编辑状态时 | 广泛支持,但不只适用于表单 |
:enabled / :disabled | 表单控件是否启用或禁用时 | 广泛支持 |
:fullscreen | 元素处于全屏状态时 | 现代浏览器广泛支持,旧版本可能需要前缀 |
:blank | CSS Selectors Level 4 中用于匹配用户为空的表单控件 | 尚未广泛实现,不应直接依赖 |
例如,:not 需要传入一个选择器参数,不能单独写成 :not:
button:not([disabled]) {
cursor: pointer;
}
:empty 判断的是子节点,而不仅仅是“没有元素子节点”。下面的空白字符也属于文本节点:
<div class="empty"> </div>
伪元素有哪些?
与伪类相比,常见的标准伪元素数量较少:
| 伪元素 | 作用 | 当前支持概览 |
|---|---|---|
::first-letter | 选择块容器等区域中的首字母或首个文字片段 | 广泛支持 |
::first-line | 选择元素排版后的第一行 | 广泛支持 |
::before | 在元素内容前生成一个伪元素 | 广泛支持 |
::after | 在元素内容后生成一个伪元素 | 广泛支持 |
::placeholder | 设置表单控件的占位文本样式 | 广泛支持,但可设置属性有限 |
::file-selector-button | 设置 input[type="file"] 的文件选择按钮 | 现代浏览器支持较好 |
::selection | 设置用户选中的文本区域样式 | 广泛支持,可设置属性有限 |
::backdrop | 设置全屏、对话框或 Popover 等顶层元素后面的背景层 | 现代浏览器支持较好 |
::marker | 设置列表项的项目符号或编号标记 | 现代浏览器支持较好 |
::cue | 设置视频 WebVTT 字幕提示的样式 | 现代浏览器支持情况不完全一致 |
::part() | 设置 Shadow DOM 暴露出来的 part | 现代浏览器支持较好 |
::slotted() | 设置 Shadow DOM 插槽中的分发节点 | 需要 Web Components 场景 |
::before 和 ::after 通常需要设置非 none 的 content 值,否则不会生成可见的伪元素:
.notice::before {
content: '提示:';
color: #c2410c;
}
伪元素适合装饰性内容。对用户有实际意义的文字、错误信息和操作提示不应只放在伪元素中,否则可能无法被辅助技术正确读取。
经典案例分析
添加前缀或后缀
伪元素最常见的用途之一,是利用 ::before 和 ::after 为元素增加装饰性前缀或后缀。
<div class="error-message">
系统异常,请稍后再试
</div>
.error-message {
position: relative;
padding: 12px 30px;
color: #666;
background-color: #ffece4;
border-radius: 5px;
}
.error-message::before {
position: absolute;
top: 50%;
left: 10px;
width: 15px;
height: 15px;
color: #c2410c;
font-weight: 700;
line-height: 15px;
text-align: center;
content: '!';
transform: translateY(-50%);
}
如果项目使用 public/icon-error.svg,在 Vite 或 Nuxt 中通常应通过 /icon-error.svg 访问,而不是 /public/icon-error.svg。

注意:
::before和::after需要合适的content才能生成;- 绝对定位的伪元素通常需要宿主元素建立定位包含块,例如设置
position: relative; - 纯装饰图标可以使用伪元素,但关键错误信息不能只依赖伪元素传递;
- 如果使用图标字体或背景图片,仍需考虑高对比度和加载失败时的表现。
消息气泡
聊天界面经常会使用带箭头的消息气泡。可以通过 ::after 生成零尺寸边框三角形:
<div class="container">
<div class="box top-arrow">Top</div>
<div class="box right-arrow">Right</div>
<div class="box bottom-arrow">Bottom</div>
<div class="box left-arrow">Left</div>
</div>
.container {
display: grid;
grid-template-columns: 200px 200px;
grid-template-rows: 200px 200px;
gap: 20px;
}
.box {
position: relative;
display: flex;
width: 150px;
height: 100px;
align-items: center;
justify-content: center;
color: white;
font-size: 16px;
background-color: red;
border-radius: 20px;
}
.box::after {
position: absolute;
width: 0;
height: 0;
content: '';
}
/* 箭头位于盒子上方,并指向上方 */
.box.top-arrow::after {
bottom: 100%;
left: 22px;
border-right: 8px solid transparent;
border-bottom: 12px solid red;
border-left: 8px solid transparent;
}
/* 箭头位于盒子右侧,并指向右方 */
.box.right-arrow::after {
top: 22px;
left: 100%;
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-left: 12px solid red;
}
/* 箭头位于盒子下方,并指向下方 */
.box.bottom-arrow::after {
top: 100%;
left: 22px;
border-top: 12px solid red;
border-right: 8px solid transparent;
border-left: 8px solid transparent;
}
/* 箭头位于盒子左侧,并指向左方 */
.box.left-arrow::after {
top: 22px;
right: 100%;
border-top: 8px solid transparent;
border-right: 12px solid red;
border-bottom: 8px solid transparent;
}

原文示例中的箭头位置和 top-arrow、bottom-arrow 等 class 名称存在对应关系混乱,已按照“箭头所在边”和“箭头指向”重新整理。
斑马条纹效果
在列表中,可以使用 :nth-child(even) 为偶数行设置不同颜色:
<div class="stripe-list">
<div>第 1 行</div>
<div>第 2 行</div>
<div>第 3 行</div>
<div>第 4 行</div>
<div>第 5 行</div>
<div>第 6 行</div>
</div>
.stripe-list {
width: 200px;
margin: 50px auto;
border: 1px solid antiquewhite;
}
.stripe-list > div {
padding: 5px;
}
.stripe-list > div:nth-child(even) {
background-color: antiquewhite;
}

:nth-child(even) 按所有同级子节点计数。如果只想按照同类型元素计数,可以使用 :nth-of-type(even)。
悬浮高亮效果
在商品列表中,当用户将指针悬浮到商品卡片上时,可以通过阴影和缩放突出当前卡片:
<div class="product-list">
<a class="product" href="/product/1" aria-label="商品 1"></a>
<a class="product" href="/product/2" aria-label="商品 2"></a>
<a class="product" href="/product/3" aria-label="商品 3"></a>
</div>
body {
margin: 0;
background-color: rgb(245 245 245);
}
.product-list {
display: flex;
width: 360px;
height: 200px;
margin: auto;
align-items: center;
justify-content: space-around;
}
.product {
width: 100px;
height: 100px;
background-color: white;
background-image: url('./images/pseudo-product.webp');
background-position: center;
background-size: cover;
transition: transform 180ms ease, box-shadow 180ms ease;
}
.product:hover {
z-index: 1;
box-shadow: 0 15px 30px rgb(0 0 0 / 10%);
transform: translateY(-5px) scale(1.2);
}
.product:focus-visible {
outline: 3px solid #2563eb;
outline-offset: 3px;
}

如果元素可以点击,建议使用 <a> 或 <button>,不要只给普通 div 添加 cursor: pointer。同时还应提供键盘焦点样式,不能只依赖鼠标悬浮。
自定义复选框样式
可以使用表单状态伪类 :checked 和伪元素 ::before 自定义复选框。示例需要保证每个 id 唯一,并且 label 的 for 与之对应:
<div class="checkbox-list">
<div>
<input class="checkbox-input" type="checkbox" id="backend-1">
<label class="checkbox-label" for="backend-1">后端开发</label>
</div>
<div>
<input class="checkbox-input" type="checkbox" id="backend-2">
<label class="checkbox-label" for="backend-2">后端开发</label>
</div>
<div>
<input class="checkbox-input" type="checkbox" id="frontend-1">
<label class="checkbox-label" for="frontend-1">前端开发</label>
</div>
<div>
<input class="checkbox-input" type="checkbox" id="frontend-2">
<label class="checkbox-label" for="frontend-2">前端开发</label>
</div>
</div>
.checkbox-input {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
clip-path: inset(50%);
white-space: nowrap;
}
.checkbox-label {
display: inline-flex;
gap: 0.5em;
align-items: center;
cursor: pointer;
}
.checkbox-label::before {
display: inline-grid;
width: 0.9em;
height: 0.9em;
place-content: center;
color: white;
font-size: 0.8em;
background-color: silver;
border-radius: 0.2em;
content: '';
}
.checkbox-input:checked + .checkbox-label::before {
background-color: yellowgreen;
content: '✓';
}
.checkbox-input:focus-visible + .checkbox-label::before {
outline: 2px solid #2563eb;
outline-offset: 2px;
}

不建议简单使用 opacity: 0 并禁用 label 的点击事件,因为这样可能造成焦点不可见、标签无法点击和辅助技术体验不佳。对于不需要完全自定义外观的场景,也可以直接使用浏览器原生控件的 accent-color。
自定义滚动条样式
部分浏览器允许使用非标准的 ::-webkit-scrollbar 系列伪元素自定义滚动条。该方案主要适用于 Chromium 和部分 WebKit 浏览器,不是跨浏览器统一标准。
<div class="scroll-demo">
<div class="scroll-box">
<div class="scroll-content"></div>
</div>
<div class="scroll-box custom-scrollbar">
<div class="scroll-content"></div>
</div>
</div>
.scroll-demo {
display: flex;
justify-content: space-around;
gap: 1rem;
}
.scroll-box {
width: 200px;
height: 200px;
overflow: auto;
border: 1px solid gainsboro;
}
.scroll-content {
width: 100px;
height: 300px;
background-color: aliceblue;
}
.custom-scrollbar {
scrollbar-color: rgba(0 0 0 / 20%) transparent;
scrollbar-width: thin;
}
.custom-scrollbar::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: rgba(0 0 0 / 20%);
border: 3px solid transparent;
border-radius: 999px;
background-clip: padding-box;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: rgba(0 0 0 / 35%);
}

原文称滚动条默认宽度为 6px、悬浮后变成 10px,但示例实际上始终设置为 10px,并没有实现宽度变化。滚动条尺寸和样式还可能被操作系统、浏览器设置和强制颜色模式覆盖,不应设置得过窄或只依赖自定义滚动条传达信息。
常见伪类示意图

伪元素
伪元素是附加在选择器末尾的关键词,允许对被选择元素的特定部分设置样式,或者生成不在文档树中的渲染内容。
按照 CSS3 之后的规范,伪元素推荐使用双冒号,例如 ::before 和 ::after,以便与伪类区分。为了兼容旧代码,::before、::after、::first-letter 和 ::first-line 等早期伪元素在许多浏览器中也支持单冒号写法。
CSS 新增的伪元素通常不应使用单冒号替代双冒号。项目代码统一使用双冒号更清晰:
.card::before {
content: '';
}
常见伪元素包括:
::before/:before:在元素内容前生成伪元素;::after/:after:在元素内容后生成伪元素;::first-letter/:first-letter:设置首字母或首个文字片段;::first-line/:first-line:设置排版后的第一行;::marker:设置display: list-item元素的项目符号或编号;::selection:设置用户选中文本的样式;::placeholder:设置输入框占位文本的样式;::file-selector-button:设置文件输入控件的按钮;::backdrop:设置顶层元素后面的背景层。
伪元素和伪类的区别
语法区别
CSS2 时代部分伪元素和伪类都使用单冒号。CSS3 为了清晰区分两者,推荐伪类使用单冒号,伪元素使用双冒号:
button:hover {
background: #2563eb;
}
button::before {
content: '';
}
早期的 ::before、::after、::first-letter、::first-line 仍然通常兼容单冒号写法,但新代码应优先使用双冒号。
含义区别
- 伪类:描述元素的状态、结构位置或满足的条件,例如
:hover、:checked、:nth-child(); - 伪元素:选择元素的特定渲染部分,或生成一个不对应真实 DOM 节点的渲染对象,例如
::first-letter、::before; - 伪类通常可以继续与其他选择器组合,用来筛选真实元素;
- 伪元素通常位于选择器末尾,表示真实元素内部的特定渲染区域;
- 伪类和伪元素都不能替代真正的语义 HTML。
选择器优先级区别
伪类的优先级通常与 class 选择器相同:
button:hover {
/* 伪类:class 选择器级别 */
}
伪元素的优先级通常与类型选择器相同:
button::before {
/* 伪元素:类型选择器级别 */
}
实际项目中还需要结合 :where()、:is()、层叠层(@layer)和选择器顺序一起判断最终样式。
使用建议
- 使用
:hover时,同时考虑键盘用户和触摸设备; - 为可交互元素提供
:focus-visible样式,不要只设计鼠标悬浮状态; ::before和::after适合装饰、图标和简单提示,不要承载关键业务文本;- 表单校验应使用真实的 HTML 属性,例如
required、disabled、aria-invalid,不能只用颜色或伪元素提示; - 自定义控件必须保留键盘操作、焦点样式和屏幕阅读器语义;
- 浏览器兼容性表需要注明目标浏览器和统计时间,不能使用永久不变的“支持/不支持”结论。