headers属性只认id,不认class、文字或位置;必须与th元素的id精确匹配(大小写、空格敏感),空格分隔多个id,优先级高于scope,且ID须在td渲染前存在于DOM中。

headers 属性只认 id,不认 class、文字或位置
它不是靠“上下左右”自动推断表头,也不是匹配 class 名或单元格内容。浏览器和屏幕阅读器只通过 headers 的值(比如 "name email")去 DOM 中查找对应 id 的 <th> 元素。哪怕你写 <code>headers="姓名",但没有 <th id="姓名">,就完全失效。<p>常见错误包括:</p>
<ul><li>
<code><th class="name">姓名</th> 配 <td headers="name"> —— 不生效<li>
<code><th id="Name">姓名</th> 配 <td headers="name"> —— 大小写不一致,不匹配<li>
<code><th id="name">姓名</th> 被 v-if 或 ngIf 动态移除,但 <td headers="name"> 还在 —— ID 不存在,关联断裂<h3>多个表头绑定用空格分隔,不是逗号或竖线</h3>
<p><code>headers 的值是空格分隔的 id 列表,例如 headers="dept team level" 表示该单元格同时归属三个维度的表头。每个 id 必须唯一且真实存在,重复或拼错一个都会导致部分语义丢失。
典型场景是复合表头:
立即学习“前端免费学习笔记(深入)”;
将 Markdown 文章转换为微信公众号兼容的纯 HTML 格式。当用户要求"转成微信格式"、"生成公众号文章 HTML"、"排版到微信草稿箱"、"微信粘贴格式"时触发。输出带内联样式的纯 HTML,微信编辑器可直接渲染,无需额外适配。支持标题、段落、列表、粗体、行内代码、表格、引用块、代码块、配图的完整转换。
<table>
<tr>
<th id="region">大区</th>
<th colspan="2" id="sales">销售额</th>
</tr>
<tr>
<th>城市</th>
<th headers="sales region">Q1</th>
<th headers="sales region">Q2</th>
</tr>
<tr>
<td>北京</td>
<td headers="region sales">120万</td>
<td headers="region sales">135万</td>
</tr>
</table>注意:headers="region sales" 和 headers="sales region" 等价,顺序无关;但不能写成 headers="region,sales" 或 headers="region|sales"。
headers 优先级高于 scope,冲突时后者被忽略
当一个 <td> 同时写了 <code>scope 和 headers,所有主流读屏器(NVDA、VoiceOver、JAWS 2022+)都只按 headers 解析,scope 完全不生效。WAI-ARIA 明确规定这个优先级。
所以别混用,尤其不要为了“兼容老版本”硬加 scope。真实问题往往出在:
- 服务端渲染时生成了
id="name",但客户端 hydration 后又动态改了 ID 或删了节点 - 组件库自动生成的
<th> 没暴露 <code>id配置项,导致你无法配对 - 用 CSS
display: none隐藏了某个<th id="hidden-header">,但它仍会被读屏器读出——造成重复播报<h3>调试必须看 Accessibility 面板,不能靠肉眼或 console.log</h3> <p>Chrome DevTools 的 **Accessibility** 标签页里,选中某个 <code><td>,右侧会显示 “Related headers”,列出它实际关联的 <code><th id>。这是唯一可信的验证方式。<p>别信这些:</p> <ul> <li>页面看起来“对齐了” → 不代表语义正确</li> <li> <code>document.querySelector('td').headers返回字符串 → 只说明属性值存在,不保证 ID 可达 - 用 VoiceOver 测试一次没报错 → 未必覆盖所有嵌套或动态场景
最容易被忽略的是:ID 必须在 <td> 渲染前就存在于 DOM 中。React/Vue 的条件渲染、Suspense fallback、SSR/CSR ID 不一致,都会让首次读取失败且无任何提示。</td>


















