带有父选择器的创意 CSS 食谱 :has()
多年来,CSS 开发人员梦想着一个 父选择器 — 一种根据子元素设置元素样式的方法。 JavaScript 和额外的类名是唯一的解决方法。然后是 :has(),这个 CSS 伪类最终使“如果这个元素包含 X,则相应地设计它的样式”成为本机现实。
截至 2026 年,所有主要浏览器都支持 :has(),开启了标记简化和创意样式的世界,而无需触及 JavaScript。
:has() 的工作原理
:has() 伪类采用相对选择器列表作为其参数,如果任何给定选择器至少匹配一个后代或同级,则匹配一个元素。
/* Style a card differently if it contains an image */
.card:has(img) {
padding: 0;
}
/* Style a label when its input is focused */
label:has(input:focus) {
color: blue;
}
/* Style a form group when any field is invalid */
.field-group:has(:invalid) {
border-color: red;
}
选择器按文档顺序进行计算,而不是反向遍历,但它可以有效地让您查看当前元素的子元素、后代元素,甚至后续的同级元素。
秘诀 1:表单验证样式
最实际的用途之一是表单状态样式。 :has() 不是在每个输入上跟踪 :valid / :invalid 并使用 JavaScript 切换父类,而是以本机方式处理它。
.form-group:has(input:invalid) .error-message {
display: block;
}
.form-group:has(input:invalid) {
border-left: 3px solid #e74c3c;
}
.form-group:has(input:valid) {
border-left: 3px solid #2ecc71;
}
.form-group:has(input:placeholder-shown) {
border-left: 3px solid #ccc;
}
此代码片段纯粹根据输入状态显示/隐藏错误消息和颜色边框,使用零 JavaScript。
秘诀 2:动态卡片布局
带有图像的卡片通常需要与纯文本卡片不同的填充。 :has() 使这变得微不足道。
.card {
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card:has(img) {
padding: 0;
}
.card:has(img) .card-content {
padding: 16px 24px 24px;
}
.card:has(.badge) {
position: relative;
overflow: visible;
}
现在,当卡片包含图像或徽章时,它会自动调整其布局,而无需模板级条件。
秘诀 3:具有兄弟姐妹意识的星级评定
:has() 选择器可与子组合器和同级组合器一起使用,从而启用诸如在评级小部件中突出显示前面的星星之类的模式。
.star-rating {
display: flex;
flex-direction: row-reverse;
}
.star-rating label {
cursor: pointer;
color: #ccc;
}
.star-rating label:has(~ label:checked),
.star-rating label:checked {
color: gold;
}
在 :has() 中使用通用同级组合器 ~,您可以根据 DOM 中稍后出现的同级元素设置样式元素 — 如果没有 JavaScript,这种模式以前是不可能实现的。
秘诀 4:过滤和图库布局
创建一个可过滤的图库,其中根据数据属性隐藏或显示项目,无需 JavaScript 事件处理程序。
.gallery-item {
display: block;
}
/* Hide items that do NOT have the 'active' class when filter is on */
.gallery:has(.filter-active) .gallery-item:not(.active) {
display: none;
}
.gallery:has(.filter-active) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
通过将 .filter-active 类应用到过滤器按钮容器,您可以切换整个图库布局。
秘诀 5:导航和下拉菜单
传统的纯 CSS 下拉菜单需要基于悬停的技巧。使用 :has(),您可以构建可访问的、状态感知的导航。
.nav-item:has(.dropdown-menu) {
position: relative;
}
.nav-item:has(.dropdown-menu:hover),
.nav-item:has(.dropdown-trigger:focus-within) {
background: #f0f0f0;
}
.nav-item:has(.dropdown-menu) .dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
}
.nav-item:has(.dropdown-trigger:hover) .dropdown-menu,
.nav-item:has(.dropdown-trigger:focus-within) .dropdown-menu {
display: block;
}
仅使用 CSS,当悬停在触发器或菜单本身时,菜单将保持打开状态。
秘诀 6:表格行突出显示
当特定单元格包含值时突出显示整个表格行。
tr:has(td.status-error) {
background: #fde8e8;
}
tr:has(td.status-success) {
background: #e8fde8;
}
tr:has(td.amount-high) td.amount {
font-weight: bold;
color: #e67e22;
}
这对于仪表板和管理面板特别有用,其中行级视觉提示可提高可浏览性。
性能考虑因素
虽然 :has() 很强大,但如果过度使用或应用于大型 DOM 树,它的成本可能会很高。
- 避免深度嵌套 —
:has(div span p)触发复杂的子树检查 - 使用简单的选择器 - 更喜欢
:has(img)而不是:has(img[src^="https"].lazy) - 限制范围 — 将
:has()应用于容器,而不是文档根 - 浏览器优化 — 现代引擎速度很快,但大页面上的数千个
:has()选择器仍然需要成本
/* Prefer this */
.list-item:has(.active) { background: blue; }
/* Over this */
body :has(.active) { background: blue; }
根据经验,使用 :has() 可以降低 JavaScript 复杂性,但不要用它替换每个 JS 模式。
结论
:has() 伪类是多年来最重要的 CSS 添加之一。它消除了无数的 JavaScript 解决方法,简化了模板,并实现了以前在纯 CSS 中不可能实现的样式模式。
通过在表单、卡片、导航、表格和图库中采用 :has(),您可以编写更少的代码,交付更少的 JavaScript,并构建更易于维护的界面。随着浏览器支持达到 100%,今天没有理由不开始使用 :has() 。

