html标签嵌套有严格解析规则:内不能放,下必须是,漏会触发浏览器静默修复,导致dom结构改变、样式失效、js选择器失配及ssr hydration失败。

<p></p> 里不能放 <div>,<code><ul></ul> 下必须是 <li>,<table> 里漏掉 <code><tbody> 就会触发浏览器静默修复——这些不是“写法偏好”,而是浏览器解析 HTML 的硬性规则。违反后不报错,但结构已变,样式和 JS 都可能失效。
<h3>为什么 <code><p></p> 里塞 <div> 会导致样式失效
<p>浏览器遇到 <code><p></p>
<div>xxx</div>
<p></p>,再把 <div> 放到外面,最后补一个空 <code><p></p>。实际 DOM 变成:<p>xxx</p>
<div>xxx</div>
<p></p>。
后果很直接:
- CSS 中
p > div选择器完全不匹配(因为<div> 已不在 <code><p></p>内) - 原本靠
<p></p>的margin或padding控制的垂直间距消失 - JS 用
document.querySelector('p')可能拿到意外的空段落节点 - 若页面依赖服务端渲染(SSR),客户端 hydration 会失败
- 默认项目符号(
list-style)不显示 -
ul li类型选择器失效,必须改写为ul + div.item这类脆弱写法 - 语义丢失:屏幕阅读器无法识别这是列表项
- 后续加 JS 动态插入
<li>时,DOM 结构不一致 -
<h1></h1>~<h6></h6>和<dt></dt>也只接受行内内容,放<p></p>或<div> 同样触发自动闭合 <li> <code><button></button>内不能嵌套<input>、<select></select>、<textarea></textarea>等可交互元素(会禁用表单提交或焦点逻辑) - 所有 void 元素(
<img>、<input>、<br>)后面加不是“多写个标签”,而是创建了一个未知自定义元素,后续 DOM 会整体缩进错位
<ul></ul> 和 <ol></ol> 只能直接包 <li>
写成 <ul><div class="item">xxx</div></ul> 是非法的。浏览器会把 <div> 移出 <code><ul></ul>,变成兄弟节点,<ul></ul> 内部只剩一个空列表。
这导致:
<table> 必须带 <code><thead>/<code><tbody>/<code><tfoot><p><code><table><tr><td>A</td></tr></table> 看似能运行,但 W3C 要求 <tr> 必须在 <code><thead>、<code><tbody> 或 <code><tfoot> 内部。浏览器会自动补 <code><tbody>,但补的位置不可控。
<p>常见副作用:</p>
<ul><li>用 CSS 选中第一行(如 <code>tr:first-child)可能选到意外的 <tbody> 而非 <code><tr><li>JS 操作 <code>table.tBodies[0].rows 时,如果手写 HTML 漏了 <tbody>,服务端与客户端 DOM 层级可能不一致
<li>某些 CSS 框架(如 Bootstrap Table)依赖显式 <code><tbody> 做样式隔离,漏写就导致边框/悬停失效
<h3>行内元素嵌套块级元素的典型陷阱</h3>
<p><code><a></a> 是特例:它被允许包裹 <div>、<code><h3></h3>、<img> 等任意元素(HTML5 允许),但 <span></span>、<strong></strong>、<em></em> 绝对不行。
例如:<span><div>xxx</div></span> 会被浏览器拆解成 <span></span><div>xxx</div>
<span></span>,造成布局断裂。
真正容易被忽略的是:











