正确做法是用<thead>和<tbody>语义化分组、scope属性明确行列关系、价格用<span class="price">包裹、保持DOM完整不隐藏。

直接用 <thead> 区分表头、<tbody> 按逻辑分组数据行,再配合 scope 属性和语义化 <th>,就能让分类清晰可读、可访问、可维护——别靠视觉对齐或空行“假装”分类。
用 <tbody> 划分数据类别,而不是靠空行或 class 名
很多人想展示“基础版”“企业版”“旗舰版”三类价格,就写三个 <tr>,中间加个空 <tr><td colspan="4"> </td></tr> 隔开。这看着像分类,但 JS 无法识别、屏幕阅读器跳过、CSS 选中困难。
- 正确做法:每个版本包进独立的
<tbody>,并加class标识用途,比如<tbody class="plan-basic"> -
document.querySelectorAll('tbody').length能准确返回分类数,tbody.plan-enterprise tr可精准样式控制 - 浏览器即使省略
<tbody>也会自动补全,但手动写出才能真正“按类操作”
scope 属性决定“这个表头管哪一列/哪一行”
只用 <th> 不够,不加 scope,辅助工具不知道“企业版”是行名还是列名,“年付”到底对应哪几行价格。分类表格里,行列语义必须明确。
- 第一列是产品名称 → 每个
<th scope="row">(如<th scope="row">企业版</th>) - 第一行是计费周期 → 每个
<th scope="col">(如<th scope="col">月付</th>) - 混用
scope="row"和scope="col"后,aria-labelledby自动生效,读屏软件能说出“企业版 月付 ¥499”而非“第3行第2列 ¥499”
价格数字必须包裹在 <span class="price"> 里
把价格当纯文本塞进 <td>,后续加促销标签、多币种切换、动态计算折扣时就得重写整行 HTML。提前结构化,成本几乎为零,收益巨大。
立即学习“前端免费学习笔记(深入)”;
- 写成
<td><span class="price">¥499</span>/月</td>,不是<td>¥499/月</td> - 这样 CSS 可单独给
.price加粗、变色、加text-decoration: line-through - JS 获取价格只需
row.querySelector('.price').textContent,不怕单位干扰 - 如果要支持汇率换算,只改
.price内容即可,不用动<td>结构
移动端保留全部 DOM,靠 CSS 重排而非隐藏列
常见错误是用 @media 直接 display: none 掉“年付”列,结果屏幕阅读器跳过、打印时消失、JS 读不到原始数据。分类表格尤其不能删结构。
- 保持所有
<th>和<td>在 DOM 中,哪怕在小屏下视觉上折叠 - 用
flex-direction: column+order重排单元格顺序,或用grid-template-rows控制显示流 - 必要时加
aria-hidden="true"配合视觉隐藏,但绝不移除元素 - 测试点:打开系统朗读功能,确认“基础版 年付 ¥999”仍能被完整播报
最容易被忽略的是 <tbody> 的显式书写和 scope 的强制使用——它们不改变页面外观,却决定了表格能否被 JS 精准操作、被辅助技术正确理解、被后续需求平滑扩展。结构没立住,后面所有样式和交互都是在补漏。



















