现代选择器的意义不是「炫技」,而是少写权重、少写类名

① :is() / :where():把选择器体积收敛掉

:is() 里的 h2

h3 同样命中

.lead 也命中

:is() 的这一行颜色是紫

:where() 时,旁边一条 h2 规则就能把它改红

/* 展开写法:4 个前缀 × 3 个后缀 = 12 条 */
.card .title, .card .meta, .panel .title, .panel .meta, … { font-weight: 700; }
/* 收敛写法:一条 */
:is(.card, .panel) :is(.title, .meta) { font-weight: 700; }

/* 唯一但关键的差别:权重
   :is(A, B) 的权重 = 参数里最高的那个  →  (0,1,0)
   :where(A, B) 的权重 = 0            →  适合写「组件默认样式、使用方随手可覆盖」 */
:where(h2, h3) { color: purple; }     /* 任何 .x h2 都能盖过它 */

解析风险::is() 里出现浏览器无法解析的部分会导致整条规则失效;:where() 同样严格。所以不要把「很新的选择器」塞进 :is() 参数里当降级手段,那种情况用 @supports 或独立规则。

② :has():真正的父向选择器

带 .badge 的子项3
父级用它加右内边距,无需 JS 加类
这个 item 里有图片就变虚线
.item:has(input:checked) { border-color: #0969da; }        /* 状态上移到容器 */
.card:has(img) { padding-top: 0; }                        /* 有封面图的卡片去掉顶部留白 */
li:has(> ul) { font-weight: 700; }                        /* 「有子列表」的项加粗 */
.tr:has(.cell__total) { background: #fff8c5; }
body:has(#search:focus) .hero { opacity: .35; }           /* 页面级联动:谨慎使用,性能与可读性都要克制 */

/* 参数里可以写相对关系:
   :has(+ .next)  后面紧跟某元素的兄弟
   :has(~ .x)     之后同级存在 .x
   :has(> .x)     直接子元素 */

浏览器基线::has() 已在 Chrome / Edge / Safari / Firefox 全量支持(Firefox 121 起),属 Baseline 可用;它会让浏览器在结构变化时重算,避免在超大列表上写高成本的选择器链。

③ :not() 可以传多个参数

主按钮
第三项
/* 老写法只能一个参数,得串联 */
.btn:not(.primary):not([disabled]) { … }
/* 新写法逗号即可(等价 and) */
.btn:not(.primary, [disabled]) { … }
/* 权重仍取 :not 内部最高项(不是 0),别把它当 :where 用 */

④ :nth-child(An+B of S):在「某类元素里」数第几个

li:nth-child(2 of .tag) { color: #bf8700; }   /* 「.tag 里的第 2 个」 */
li:nth-child(odd of :is(.tag, .note)) { … }   /* 在两个类的合集里奇偶交替 */
tr:nth-child(3n of td.has-total) { … }        /* 常见旧需求:跳过表头后再算奇偶 */

以前要实现它只能让 JS 加类。浏览器基线:这一形态同样已是主流内核全量支持,Firefox 113 / Chrome 111 / Safari 9 一线起。

⑤ 焦点、表单与结构状态

用鼠标点下面两个控件不会看到焦点环;按 Tab 键盘移动时才会出现 —— 这正是 :focus-visible 的用途。

结构类与语言类示例:下面列表的首项加粗、末两项变蓝(:first-child / :nth-last-child(-n+2));同一句话按 lang 不同走不同字形规则(:lang());点这个锚点会看到 :target 把目标条变黄。

日本語のテキスト:行間に調整が必要

中文文本:字距略微拉开更易读

:target 命中时才有的高亮条

选择器含义
:focus-visible只在「浏览器认为该显示焦点环」时命中(键盘导航),替代写死的 outline: none
:focus-within后代任一元素获得焦点时命中容器,做搜索框整块高亮很省
:valid / :invalid表单校验状态,但「未交互的空必填项」也算 invalid
:user-invalid只在用户改过之后才判无效(Chrome 119 / Safari 16.5 / Firefox 88 一线),比 invalid 更合适做红框
:placeholder-shown浮动 label 的老配方(现在更推荐 :has() 或 :not(:placeholder-shown))
:checked / :default / :indeterminate半选状态 :indeterminate 只能由 JS 设置
:open<details> 展开时命中,纯 CSS 折叠面板的状态源
:empty无子节点且无文本(注意空白字符会影响判定)
:targetURL 的 hash 指向它时命中,做「定位高亮」零成本
:nth-last-child(-n+2)倒数两项
:lang(zh)按 lang 属性匹配,中文/日文的标点与字体差异靠它

⑥ calc() 与它的兄弟函数

宽度 calc(100% - 2 * 16px)
字号 calc(1rem + 0.5vw)
内边距 max(12px, calc((100% - 640px) / 2))
上边距 calc(var(--n) * 6px)
/* 混合单位必须靠 calc():100% 与 16px 不能直接相减写在声明里 */
.el { width: calc(100% - 2 * var(--gutter)); }
/* 规则:+ - 两侧要空格;* / 的数字必须是纯数字,不能带单位 */
/* 常见错:calc(10px*2) 在部分老内核里因缺空格被当成属性值解析失败 */
.el { width: calc(100% / 3 - 8px); }
.el { font-size: clamp(1rem, calc(0.9rem + 0.4vw), 1.1rem); }  /* 第十三章 */
/* round()/mod()/sign()/hypot()/tan() 等数学函数已进入主流内核,用之前先 @supports 检查 */

⑦ 什么时候不该用现代选择器

  1. 用 :has() 实现「需要 JS 才能算出的业务状态」——状态本身该由 JS 写类,选择器只是表达样式。
  2. 在 :is() 里混入不支持的选择器,导致整条规则被丢弃(见①的风险说明)。
  3. 用超长选择器链匹配表格每一行以「提高权重」——权重问题请回第四章,用更合理的类名结构解决。
  4. 依赖 :scope、命名 @scope 做常规样式:仍在演进,写教程外的项目里请谨慎。