:first-child 仅当目标元素是父元素的首个子节点时生效,它严格依据 dom 树中 parentnode.childnodes[0] 判断,无视语义、类型或视觉顺序,空格、换行、注释等文本节点均会导致匹配失败。

:first-child 只在目标元素真是父元素第一个子节点时才生效,否则它根本不会匹配——这不是 bug,是设计如此。
为什么 :first-child 经常“不生效”
它不看语义、不看类型分组,只认 DOM 树里 parentNode.childNodes[0] 是不是你写的那个标签。哪怕前面只有一个换行、一个空格、一条 HTML 注释 <!-- intro -->,或者一个 <header></header>,后面的 p:first-child 就立刻失效。
- 常见错误现象:
ul li:first-child没加边框,检查 Elements 面板发现ul下第一个节点是文本节点(换行缩进产生) - JS 动态插入前置内容后,原来生效的
div:first-child突然不触发样式 -
article > p:first-child在有标题的正文里完全没反应——因为第一个子节点是<h2></h2> - 服务端模板(如 Django、Nunjucks)悄悄注入空格或注释,导致构建后样式错乱
:first-child 和 :first-of-type 到底该用哪个
选谁,取决于你要解决的问题:
- 要“结构上第一个子元素”(比如清掉容器顶部默认 margin)→ 用
:first-child,但必须确保 HTML 干净:删空格、去注释、禁用模板自动换行 - 要“第一个出现的某类元素”(比如文章里首个
<p></p>、列表中首个<li>)→ 直接用:first-of-type,它无视中间夹杂的其他标签,按类型单独计数 -
div:first-child和div:first-of-type在<div>...</div> <p>...</p> <div>...</div>中结果不同:前者不匹配,后者匹配第二个div - IE9+ 支持
:first-of-type,老项目若需兼容 IE8,只能靠 JS 添加class="first"或服务端标记
调试和验证 :first-child 是否命中
别猜,直接看 DOM 结构:
- 打开浏览器 DevTools → Elements 面板 → 展开父元素 → 数子节点顺序,确认目标元素是否真为
childNodes[0] - 右键父元素 → “Edit as HTML” → 手动删掉开头的注释或空行 → 刷新看样式是否恢复
- 临时加测试样式:
p:first-child { outline: 2px solid red; },如果没红框,说明没命中 - 注意:CSS 伪类无法穿透 Shadow DOM;自定义组件内部写
:first-child,只对 shadow root 内部有效
真正稳妥的替代方案
纯 CSS 有时扛不住动态结构或复杂嵌套:
- 显式加 class:
<li class="first">…</li>,比依赖结构更可控,也利于可访问性(如aria-labelledby关联) - JS 动态标记:
parent.children[0]?.classList.add('first'),适合 AJAX 加载后的内容或 Web Component 初始化 - 用
:has()(Chrome 105+、Safari 15.4+)做条件判断:section:has(> p:first-of-type),但目前兼容性仍需谨慎评估
最常被忽略的一点:所谓“第一个”,从来不是视觉上的,而是 DOM 节点顺序上的——哪怕你用 flex-direction: column-reverse 把元素翻转到页面底部,:first-child 依然作用于原始第一个子节点。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











