2026 年 CSS 选择器完整指南

CSS1周前发布 admin
130 0

如果你已经写了好几年 CSS,可能会觉得”选择器”这种基础话题没什么好聊的——不就是 .class#iddiv > p 那一套吗?但这几年 CSS 选择器其实悄悄发生了一次大升级,很多以前只能靠 JavaScript 才能实现的效果,现在一行选择器就能搞定。

这篇文章不讲那些人人都会的基础知识,而是把 2026 年真正值得你用起来的选择器捋一遍,顺便告诉你什么时候能放心用、什么时候还得留个心眼。

一、基础选择器:三句话带过

这部分默认你已经会了,快速过一遍防止漏掉细节:

  • 类型/类/ID 选择器div.card#header,优先级依次升高。
  • 组合器A B(后代)、A > B(子元素)、A + B(紧邻兄弟)、A ~ B(普通兄弟)。
  • 优先级计算:内联样式 > ID > 类/属性/伪类 > 标签/伪元素,同级别看谁写在后面。

真正拉开差距的,是下面这些”现代选择器”。

二、分组免责声明::is():where()

写多了 CSS 都遇到过这种重复代码:

header h1,
header h2,
footer h1,
footer h2 {
  font-family: serif;
}

:is() 可以直接压缩成一行:

:is(header, footer) :is(h1, h2) {
  font-family: serif;
}

:is():where() 功能几乎一样,唯一的区别是优先级计算方式不同:

  • :is() 会取括号里”优先级最高的那个选择器”作为整体优先级。
  • :where() 无论里面写什么,优先级永远是 0。

这个区别很关键。如果你在写一套组件库或者设计系统,想让使用者能轻松用一个类名覆盖你的样式,那就该用 :where(),因为它不会占用优先级,后来者一个 .my-override 就能盖过去。

反过来,如果你希望这段样式有一定的”分量”,不容易被意外覆盖,就用 :is()

它们还有个隐藏福利,叫”宽容选择器列表”(forgiving selector list)——括号里就算有一个浏览器不认识的选择器,整条规则也不会全部失效,只是那一部分不生效。

普通的逗号分组选择器可做不到这点,只要有一个写错或者浏览器不支持,整条规则直接作废。

三、真正的”父选择器”::has()

这是这几年 CSS 里公认最大的突破。CSS 选择器一直只能”往下看”或者”往旁边看”(子元素、兄弟元素),没法”往上看”选中父元素——直到 :has() 出现。

/* 一个 figure 里如果有 figcaption,就加下边距 */
figure:has(figcaption) {
  margin-bottom: 2rem;
}

/* 输入框如果是必填且无效,让相邻的 label 变红 */
label:has(+ input:required:invalid) {
  color: crimson;
}

/* 卡片里如果有图片,就用不同的排版 */
.card:has(img) {
  display: grid;
  grid-template-columns: 120px 1fr;
}

以前想实现”根据子元素状态改变父元素样式”,基本只能靠 JavaScript 监听事件、动态加类名。

现在这些场景都能用纯 CSS 声明式地写出来,不用担心时序问题,也不会有 hydration 不一致的坑。

兼容性提醒:2026 年这个选择器已经被认定为”广泛可用”(Baseline Widely Available),四大浏览器引擎全部支持多年,可以放心在生产环境使用,基本不需要额外做兼容处理。

唯一要注意的是性能——:has() 需要遍历整个子树去匹配,如果括号里的条件写得太宽泛,或者作用的容器子元素特别多,可能会拖慢选择器匹配速度。

经验法则是:让左边的”主体”元素范围尽量小,让括号里的条件尽量精确。

如果你的项目还要兼容非常老的浏览器,可以用特性检测包一层:

@supports selector(:has(a)) {
  .card:has(img) {
    display: grid;
  }
}

注意这里用的是 selector() 语法,问的是”浏览器认不认识这个选择器写法”,跟检测某个 CSS 属性支持情况的写法不太一样。

四、:nth-child() 的进化版:of 语法

大家都用过 :nth-child(2n) 选偶数行、:nth-child(odd) 选奇数行,但很多人不知道它现在还支持一个 of 从句,可以先筛选出符合某个条件的元素,再按顺序数第几个。

/* 在所有"可见"的列表项里,选出第 2 个 */
li:nth-child(2 of :not(.hidden)) {
  font-weight: bold;
}

这个写法在处理”有些元素被隐藏或被过滤掉”的列表时特别好用——以前你只能先数出所有元素的位置再排除,现在可以直接对”筛选后的子集”计数。

这个语法已经进入广泛可用阶段,主流浏览器都支持,可以放心使用。

五、原生 CSS 嵌套:不用预处理器也能写”套娃”

Sass、Less 这类预处理器最大的卖点之一就是嵌套写法,现在浏览器原生就支持了:

.card {
  padding: 1rem;

  h2 {
    font-size: 1.25rem;
  }

  &:hover {
    box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
  }

  &.card--featured {
    border: 2px solid gold;
  }

  .dark & {
    background: #1a1a1a;
  }
}

几个使用要点:

  • 简单的后代选择器(比如上面的 h2),& 可以省略,浏览器会自动理解。
  • 但如果你要把父选择器接在自己后面(比如 &:hover&.active),或者父选择器不在开头位置(比如 .dark & 表示”当祖先有 .dark 类时”),& 就必须写出来。
  • 嵌套只是”语法糖”,最终会被浏览器展开成和手写扁平选择器完全一样的优先级,不会有优先级方面的意外。
  • 嵌套规则里还能直接塞 @media@container@supports,把响应式逻辑和样式写在一起,不用再跳出去单独写一段媒体查询。

这个特性目前也已经是广泛可用状态。如果你的项目只是用 Sass 图个嵌套方便,现在其实可以考虑砍掉这层构建工具了——不过 Sass 里的变量运算、mixin、循环这些能力,原生 CSS 还是没有完全对应的替代品,该保留的还是要保留。

六、交互与可访问性相关的伪类

这几个不算全新,但很多人还是习惯性用错,顺带纠正一下:

  • :focus-visible:只在浏览器判断”用户是靠键盘/辅助设备聚焦”时生效,鼠标点击一般不会触发。用它做聚焦样式,能避免鼠标用户看到一圈莫名其妙的高亮框,同时不牺牲键盘用户的可访问性。
  • :focus-within:只要元素内部有任何子元素获得了焦点,这个元素本身就会匹配。常用来做”表单组高亮”——用户点进组内任意一个输入框,整个表单组边框变色。
button:focus-visible {
  outline: 2px solid #2563eb;
}

.form-group:focus-within {
  border-color: #2563eb;
}

七、属性选择器的几个冷门但好用的写法

/* 属性值以某个字符串开头 */
a[href^="https://"] { color: green; }

/* 属性值以某个字符串结尾 */
img[src$=".webp"] { /* ... */ }

/* 属性值包含某个子串 */
[class*="col-"] { /* ... */ }

/* 忽略大小写匹配 */
[data-status="Active" i] { /* ... */ }

最后这个 i 标志很多人不知道,加上之后属性值匹配就不区分大小写了,处理后端传来的、大小写不统一的数据字段时很方便。

八、怎么在实际项目里做选择?

给几条实用建议,不用死记硬背:

  1. 能用 :has() 解决的”根据内容改父样式”问题,别再写 JS 了。 减少一次事件监听,就少一个潜在的时序 bug。
  2. 写组件库/设计系统,优先用 :where() 包裹你的基础样式,把优先级”预算”留给使用者。
  3. 原生嵌套 + :is()/:where() 组合使用,能大幅减少选择器重复,代码可读性会提升不少。
  4. 老项目要兼容旧浏览器时,用 @supports selector(...) 做特性检测,而不是简单粗暴地判断浏览器版本号。
  5. :has() 别嵌套太深、条件别写太宽,大表格、长列表这种场景尤其要注意性能。

写在最后

CSS 选择器这几年的进化,核心思路就一个:把以前必须靠 JavaScript 才能表达的”关系”和”状态”,变成声明式的 CSS 语句。少一行 JS,就少一份心智负担,也少一个可能出错的地方。

这些选择器现在基本都已经进入了主流浏览器的”广泛可用”阶段,该用就用起来,没必要再等了。

© 版权声明

相关文章

暂无评论

none
暂无评论...