mcpField
T
tables

tables

github.com/mgifford设计与 UI57148 次安装6 周前更新
代码仓库

关于 tables

这是为创建或审查 HTML 数据表提供的无障碍指南,适用于开发者和内容设计人员。它强调正确标记表头与关联关系,并建议通过简化结构避免复杂表格难以理解。

主要功能

  1. 01区分数据表与页面布局
  2. 02使用表头及 scope 标记关联
  3. 03为表格提供标题和名称
  4. 04处理分组表头与复杂关联
  5. 05指导响应式表格、排序和筛选

适用场景

  1. 01审查 HTML 表格的语义标记
  2. 02改进复杂表格的屏幕阅读器导航
  3. 03设计可排序、筛选或分页的数据表

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mgifford/accessibility-skills --skill tables

安装后在 Claude Code 中输入/tables确认已加载。

附带文件README.mdSYNC.md

详细介绍

表格无障碍技能

规范来源:mgifford/ACCESSIBILITY.md 中的 examples/TABLES_ACCESSIBILITY_BEST_PRACTICES.md 此技能源自该文件。如有疑问,请以示例为准。

创建或审查 HTML 数据表时,请遵循以下规则。 仅当项目包含数据表时,才加载此技能。


核心要求

仅当信息的含义取决于行与列之间的关系时,才使用表格。应在 HTML 中标记这些关系,以便用户识别表格、浏览单元格,并在听取每个值时获知相关的表头。标记的复杂程度应与信息的复杂程度相匹配——简化或拆分表格通常优于不断增加复杂的表头关联。

切勿使用表格进行布局。 应改用 CSS Grid/Flexbox。除非用户确实需要类似电子表格的键盘交互,否则不要将以阅读为主的数据表改为应用式网格(role="grid")。


严重程度等级(本技能)

等级含义
严重屏幕阅读器用户完全无法理解表格数据
高缺少表头或表头关联错误;复杂表格没有导航辅助
中等页面上有多个表格时,表格没有名称;强制颜色模式下斑马纹消失且没有备用样式
轻微缺少 <thead>/<tbody>;使用了过时的 summary 属性

首先选择正确的结构

  • 列表——项目之间不依赖列关系
  • 定义列表——术语/描述配对
  • 数据表——用于阅读和比较行列数据
  • ARIA 网格/树形网格——仅用于具有单元格导航或编辑功能的应用式复合控件
  • CSS Grid/Flexbox——页面布局

不要仅为对齐表单字段、卡片、图片或页面区域而使用表格。


严重:每个数据表都必须使用带 scope 的 <th>

仅包含 <td> 单元格的表格属于严重问题——屏幕阅读器只会读出原始数据,不提供上下文。对于标记行、列或分组的单元格,应使用 <th>——切勿用加粗的 <td> 替代其视觉效果。

<table>
  <caption>Monthly sales by region, first quarter 2025</caption>
  <thead>
    <tr>
      <th scope="col">Region</th>
      <th scope="col">January</th>
      <th scope="col">February</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">North</th>
      <td>$12,400</td>
      <td>$14,200</td>
    </tr>
  </tbody>
</table>

对于只有一行或一列表头的小型明确表格,浏览器有时可以仅凭 <th> 推断表头方向。明确使用 scope="col"/scope="row" 仍是可靠的编写惯例,尤其适用于较大或外观相似的数据。<thead>/<tbody>/<tfoot> 有助于实现粘性表头、样式设置、脚本处理和打印时重复显示表头——但仅添加这些元素本身,并不能修复缺失或错误的表头关联。

如果表头行左上角单元格为空(不承担表头功能),应保留为 <td>——不要仅为填充单元格而添加“空白”等占位文本。


中等:标题和表格名称

<caption> 是为表格命名的原生方式,尤其适用于单页包含多个表格的情况。WCAG 并未要求所有情况下都必须有标题——对于简单表格,紧邻表格的标题或周边文本有时足以说明其内容。建议默认使用 <caption>,但如果其他经过测试的关联方式已能提供名称,就不要强行重复命名。

<table>
  <caption>2025 budget allocation by department</caption>
  
</table>

标题必须是 <table> 的直接子元素(位于 <thead> 之前),并且简洁明确。对于结构特殊的表格,可以用简短的摘要说明行列组织方式,而无需重复每个值:

<caption>
  Availability of holiday accommodation
  <span class="table-summary">Locations form row groups. Property types form columns.</span>
</caption>

不应使用 <table> 上过时的 HTML summary 属性——请改用可见的标题内容或附近的描述。

严重:跨列标题 — scope="colgroup" / scope="rowgroup"

仅当表格结构中确实存在相应分组(匹配的 <colgroup> 或 <tbody> 行组)时,才使用 colgroup/rowgroup 范围值:

<table>
  <caption>Quarterly revenue by product line, CAD thousands</caption>
  <colgroup><col></colgroup>
  <colgroup span="2"></colgroup>
  <colgroup span="2"></colgroup>
  <thead>
    <tr>
      <th scope="col" rowspan="2">Product</th>
      <th scope="colgroup" colspan="2">First half</th>
      <th scope="colgroup" colspan="2">Second half</th>
    </tr>
    <tr>
      <th scope="col">Q1</th><th scope="col">Q2</th>
      <th scope="col">Q3</th><th scope="col">Q4</th>
    </tr>
  </thead>
  <tbody>
    <tr><th scope="row">Hardware</th><td>1,200</td><td>1,450</td><td>1,100</td><td>1,800</td></tr>
  </tbody>
</table>

对于跨多行的行组,在分组标题上使用 scope="rowgroup" 和 rowspan。使用项目实际支持的辅助技术组合测试分组标题——如果关联关系难以理解,应拆分为更简单的表格,而不是增加更多复杂性。


严重:何时使用 headers + id(极少数情况)

仅用于复杂到 scope 会导致标题应用于错误单元格的表格:

<table>
  <caption>Accommodation availability</caption>
  <thead>
    <tr>
      <th id="location" scope="col">Location</th>
      <th id="studio" scope="col">Studio</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th id="paris" scope="row">Paris</th>
      <td headers="paris studio">11</td>
    </tr>
  </tbody>
</table>

每个 ID 在文档中都必须唯一;每个 headers 标记都必须对应一个真实的标题单元格。在使用 headers/id 之前,请考虑: 将一个表格拆分为多个表格;转置行和列;将说明材料移到网格之外;减少嵌套标题层级;提供筛选视图。即使在技术上正确,每个单元格都需要朗读三个或四个标题的表格,在实际使用中仍可能无法访问——根据 WebAIM 的说法,屏幕阅读器用户很难在实际使用中理解多层行标题或列标题。每次架构或列发生变化后,都要验证 headers/id 的关联关系。


严重:空值、缺失值和特殊值

空单元格含义模糊——可能表示零、不适用、未知、未报告,或有意留空。在需要区分时,应使用明确文本: 测量值为零时使用 0;使用 Not applicable 表示不适用;使用 No data 表示无数据;使用 Not reported 表示未报告;有意隐去的值使用 Suppressed。在标题或附近的注释中定义缩写符号(N/A、破折号);空间允许时优先使用完整文本。不要使用 &nbsp; 让空单元格看起来像有内容。说明估算值、四舍五入值、初步值或修订值——不要仅依靠颜色或字体样式来标识它们。


严重:总计、小计和页脚

当总计标签用于标识该行的值时,将其标记为标题:

<tfoot>
  <tr><th scope="row">Total</th><td>$21,500</td><td>$24,500</td></tr>
</tfoot>

不要仅通过粗体文本、缩进或背景颜色来传达小计——应加入可见文本(“小计”)并保留标题关联关系。如果总计是动态计算的,应适当公开更新状态,并确保导出的值采用相同的计算方式。


中等:响应式表格和重排

WCAG 1.4.10 允许对确实需要二维滚动的内容进行二维滚动,包括数据表格——此例外仅适用于表格本身,不适用于周围的页面。

<div class="table-scroll" role="region" aria-labelledby="sales-caption" tabindex="0">
  <table>
    <caption id="sales-caption">Monthly sales by region</caption>
  </table>
</div>
.table-scroll { max-inline-size: 100%; overflow: auto; }
.table-scroll:focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }

仅当单独标记的区域确实有助于发现内容时才使用它——不要把每个表格容器都变成地标。使用 tabindex="0" 让溢出内容可以通过键盘滚动,并提供可见的焦点指示器。

避免使用响应式 CSS 将 table/thead/tbody/tr/th/td 设置为 display: block——浏览器和辅助技术的行为各不相同,重复的 data-label 内容也可能与实际标题不同步。如果在小屏幕上确实更适合采用卡片形式,应基于相同的数据渲染真正的替代列表或定义列表视图——每次仅呈现一种视图,保留所有值和控件,并测试两种呈现方式。测试 200%/400% 缩放、较长的译文字符串和窄视口。


中等:粘性标题和列

thead th {
  position: sticky;
  inset-block-start: 0;
  background: var(--table-header-background);
  z-index: 1;
}

保留原有的语义化 <th> 元素——不要创建可聚焦的克隆标题。如果无法避免视觉克隆,应将其对辅助技术隐藏,并移除所有重复 ID。确保粘性内容不会遮挡焦点、标题或第一行数据。在高倍缩放和启用文本间距覆盖的情况下进行测试。

中等:可排序列

在每个可排序的列标题中放置原生按钮;仅在当前排序的 <th> 上放置 aria-sort (不要放在按钮上,也不要两处都放):

<th scope="col" aria-sort="ascending">
  <button type="button">
    Last name
    <span class="sort-icon" aria-hidden="true">▲</span>
  </button>
</th>

排序发生变化时:对完整数据集排序,而不只是当前可见页,除非明确说明另有做法;将 aria-sort 移至新排序的 <th>;准确使用 ascending/descending/other;更新可见的方向指示器,不能仅依赖颜色;让键盘焦点保留在已激活的按钮上;保留标题与数据单元格之间的关联。不要用 <th> 自身的点击处理程序替代按钮。


中等:筛选、搜索和结果数量

<form id="employee-filters">
  <label for="department-filter">Department</label>
  <select id="department-filter" name="department">
    <option value="">All departments</option>
  </select>
  <button type="submit">Apply filters</button>
</form>
<p id="employee-results" role="status" aria-atomic="true">Showing 24 employees.</p>

筛选后更新可见的结果数量;如果数量变化时焦点不移动,请使用 role="status";让当前筛选条件保持可见且可通过程序确定;提供清除筛选条件的明确方式;让“无结果”状态可见并被读屏器播报。不要通过实时区域播报每个发生变化的单元格。当范围或日期范围发生变化时,确保标题内容准确。


中等:分页和大型数据集

与渲染或虚拟化数千行相比,分页通常更可靠。

<nav aria-label="Employees table pages">
  <a href="?page=1" aria-current="page">1</a>
  <a href="?page=2" rel="next">Next</a>
</nav>

使用 aria-current="page" 标识当前页;基于 URL 的导航使用链接,原地更改状态使用按钮;保留排序和筛选参数;说明显示范围和总数(“第 51 至 100 行,共 842 行”);仅在时机明确且有帮助时移动焦点。“加载更多”控件需要有清晰的名称、更新结果,并确保焦点移动可预测。

虚拟化(滚动时仅渲染部分数据集)可能会影响页内查找、表格导航、行数、焦点和阅读连贯性——如果分页能满足任务需求,请优先使用分页。如果必须使用虚拟化:公开准确的总行数和总列数;公开已渲染项目的正确索引;绝不在用户未察觉的情况下回收当前获得焦点的行;当项目离开 DOM 时保留选择和编辑状态;确保标题正确关联;支持缩放、调整文本大小和可变行高;使用屏幕阅读器测试连续阅读,而不只是视觉滚动。不要仅因为存在 aria-rowcount 就声称数千行具有无障碍性。


严重:行选择和行操作

不要让整个 <tr> 变成没有名称的可点击控件——应在单元格中放置实际的复选框、链接或按钮:

<p id="select-invoice" class="visually-hidden">Select invoice</p>
<tr>
  <td><input type="checkbox" name="invoice" value="1042" aria-labelledby="select-invoice invoice-1042"></td>
  <th id="invoice-1042" scope="row">1042</th>
  <td>$1,240</td>
</tr>

复选框的计算名称会变成“选择发票 1042”。以编程方式提供选中状态,并确保在视觉上可区分,不能仅依赖颜色。对于重复的行操作,应在每个可访问名称中包含该行的标识符——避免出现几十个仅播报为“查看”或“编辑”的链接。


严重:表格中的表单和编辑

每个输入项都需要唯一标签,并包含足够的行列上下文:

<tr>
  <th scope="row">Widget A</th>
  <td>
    <label class="visually-hidden" for="widget-a-quantity">Quantity for Widget A</label>
    <input id="widget-a-quantity" name="widget-a-quantity" type="number" min="0" value="2">
  </td>
</tr>

将验证错误与特定输入项关联;出错时不要移除输入项所在的行上下文;验证后保留用户输入的值;明确提供保存、取消和撤销操作;除非已提醒用户且用户能够恢复,否则不要在焦点移动时自动保存。如果用户需要像电子表格一样在多个单元格间导航或编辑,请评估使用 ARIA 网格,而不是在数据表格上临时添加键盘处理逻辑。


中等:可展开行

<tr>
  <th scope="row">Invoice 1042</th>
  <td>$1,240</td>
  <td>
    <button type="button" aria-expanded="false" aria-controls="invoice-1042-details">
      Show details for invoice 1042
    </button>
  </td>
</tr>
<tr id="invoice-1042-details" hidden>
  <td colspan="3">
    <h3>Invoice 1042 details</h3>
    <p>Issued 4 March 2025. Payment is due 3 April 2025.</p>
  </td>
</tr>

同步更新 aria-expanded、按钮文本和 hidden。除非用户的操作明确要求将焦点移入显示的内容,否则让焦点保留在按钮上。

中等:颜色、对比度和强制颜色

斑马纹只是辅助方式,不能单独作为充分手段。不要仅用行颜色表示数据逾期/已选中/无效/已更改——应添加可见文本、图标或图案。确保表格文本和必要的非文本元素(焦点指示器、表单控件边界、排序/状态图标)符合适用的对比度要求。装饰性单元格边框和斑马纹不一定需要达到 3:1。

tbody tr:nth-child(even) { background: var(--table-stripe-background); }
@media (forced-colors: active) {
  th, td { border-block-end: 1px solid CanvasText; }
  .sort-icon { color: CanvasText; }
}

在每种受支持的主题和强制颜色模式下,测试已选中行、无效单元格、焦点、悬停、排序和粘性表头。


数据表格与 ARIA 网格

原生 <table> 支持阅读/比较,通常不会让单元格进入 Tab 焦点序列——屏幕阅读器会提供自己的表格导航。role="grid" 是一种复合控件,会让用户期待可管理的焦点、方向键导航、选择、编辑以及公开的行/列结构。仅在以下情况下使用网格:用户需要类似电子表格的单元格导航/编辑;交互模型有文档说明且已完整实现;只有预期的单元格/后代元素进入 Tab 焦点序列;方向键/Home/End 的行为可预测;行/列位置和状态准确;并且已使用相关辅助技术进行测试。不要仅仅为了让表格听起来更具交互性而添加 role="grid"——单有排序/筛选按钮,并不会让表格成为网格。优先使用原生 <table>。


布局表格——如何处理

只要可行,就移除表格并改用 CSS。对于暂时无法移除的旧式布局表格:

<table role="presentation">
  <tr><td></td></tr>
</table>

不要在其中使用 <th>、<caption> 或表格专属导航;确保内容的 DOM 顺序有意义,按线性顺序阅读时逻辑清晰;确保表单/标题/列表保留各自的原生语义;不要让表格可获得焦点,也不要为其添加表格专属 ARIA。这里的 role="none" 等同于 role="presentation"。应将此视为临时修复,而不是新的布局模式。


打印和导出

@media print {
  thead { display: table-header-group; }
  tfoot { display: table-footer-group; }
  tr { break-inside: avoid; }
}

对于导出的 PDF/电子表格/文档/演示文稿文件:保留表格结构和表头关联;添加标题/说明;在各页/工作表中重复显示表头;保留单位/筛选条件/来源/日期范围;解释特殊值和缩写;使用相应的无障碍工具独立测试导出格式。HTML 无障碍并不保证导出文件也无障碍——应分别测试并修复每种输出格式。


CMS 和框架说明

**Drupal:**应将 CKEditor 的表格插件配置为要求 <caption> 和 scope 属性。Drupal Accessibility Coding Standards 要求贡献模块/主题符合 WCAG 2.1 AA。

**WordPress/其他 CMS:**区块编辑器通常生成不带 <caption> 或 scope 的表格——应审核 CMS 生成的标记,并配置编辑器或对输出进行后处理,以添加缺失的属性。

**生成的表格(数据网格库、DataTables.js、AG Grid):**验证库是否输出 <th scope="col">、<caption> 和 <thead>——许多库默认不会输出。应配置或测试确切版本,确认作者可以创建真正的表头单元格,可以添加说明/摘要,scope/headers/唯一 ID 会得到保留,清理过程不会删除必要属性,并且导出格式会保留结构。


测试

  • 结构: 确认内容确实是表格;识别每个行标题、列标题、行组标题和列组标题;从每个数据单元格验证用户需要的标题;确认 scope/headers 生成了这些确切的关联;检查标题、单位、来源和特殊值定义;在排序或重新渲染后验证 ID 和 headers 引用
  • 键盘/视觉: 不使用指针操作排序、筛选、分页、选择、编辑和展开/收起功能;确认焦点始终可见且不会意外移动;通过键盘滚动宽表格;测试 200%/400% 缩放和窄视口;测试每种主题、强制颜色模式、打印和导出格式
  • AT: 导航到表格并确认其名称/尺寸有用;浏览具有代表性的单元格并验证朗读的标题;全面测试分组、跨单元格和不规则标题;确认排序状态、选中行、展开行、输入标签、错误和结果状态
  • 自动化: 可检测部分缺失的标题、无效的 ARIA、重复 ID、空标题、对比度和焦点问题——通常无法判断某个单元格在上下文中是否为标题、关联是否表达了预期关系,或复杂表格是否应简化。仍需逐个单元格进行人工审查。

常见问题

问题修正方法
用粗体 <td> 元素在视觉上模仿标题使用语义正确、关联正确的 <th>
将“每个 <th> 都需要 scope”绝对化为 WCAG 规则在方向重要时,将显式 scope 作为稳健做法;不要错误表述判定标准
将“每个表格都必须有标题才能符合规范”绝对化默认使用标题;同时认识到,其他经过测试的关联也可以标识简单表格
使用 scope="colgroup",但没有匹配的 <colgroup>在标记中定义列组
headers 值不再匹配当前 ID根据当前架构重新生成并验证关联
空单元格使用 &nbsp; 或含义不明的破折号明确说明零、不适用、缺失、已隐藏或其他准确含义
响应式 CSS 将所有表格元素都改为 display: block保留表格语义;使用容器溢出处理或真正的替代视图
每个滚动容器都变成区域地标仅在单独发现该区域有用时,才添加命名区域
将 aria-sort 放在排序按钮上只将其放在当前已排序的 <th> 上
只对可见页面排序,但不作说明对完整结果集排序,或清楚说明排序范围
整行都可点击在单元格中放置带名称的原生链接、按钮或复选框
重复控件只被朗读为“查看”/“编辑”在每个可访问名称中加入行标识符
添加 role="grid",但没有实现网格键盘交互模型保留原生表格,或实现完整的复合控件
虚拟行在包含焦点时消失保留焦点和状态,或使用分页
HTML 表格通过测试,但导出的 PDF 丢失标题单独测试并修复每种输出格式

完成定义检查清单

  • 内容需要行列关系,并且不是布局表格
  • 每个功能性表头都是带有正确关联的 <th>
  • 表格有实用的名称,通常通过 <caption> 提供(或使用经过测试的替代方案)
  • 复杂结构有简洁且易于查找的说明
  • scope、分组、跨度、ID 和 headers 引用与当前数据相符
  • 当复杂度妨碍理解时,简化或拆分表格
  • 可区分空值、缺失值、零值、抑制值和估算值
  • 单位、日期、货币和缩写含义清晰
  • 宽表格可在受限且支持键盘操作的区域内滚动
  • 响应式呈现保留表格语义,或提供真正等效的视图
  • 粘性表头不会重复语义或遮挡内容
  • 仅在正确的 <th> 上设置排序状态;排序控件支持键盘操作
  • 筛选、分页、选择、编辑和展开控件使用带名称的原生控件
  • 颜色不是指示含义或状态的唯一方式
  • 仅在具备完整且经过测试的网格交互模型时使用 role="grid"
  • 虚拟化(若使用)能保留数量、位置、焦点和阅读连贯性
  • 结构和关系在打印和导出时仍能保留
  • 已使用 NVDA+Chrome、JAWS+Chrome、VoiceOver+Safari 进行测试

关键 WCAG 成功标准

  • 1.3.1 信息和关系(A)——缺少表头或表头关联错误时属于关键问题
  • 1.3.2 有意义的顺序(A)
  • 1.4.1 使用颜色(A)
  • 1.4.3 对比度(最低)(AA)
  • 1.4.10 重排(AA)
  • 1.4.11 非文本对比度(AA)
  • 2.1.1 键盘(A)
  • 2.4.3 焦点顺序(A)
  • 2.5.3 标签(名称中)(A)
  • 2.5.8 目标尺寸(最低)(AA)
  • 3.3.1 错误识别(A)——适用于可编辑表格
  • 4.1.2 名称、角色、值(A)
  • 4.1.3 状态消息(AA)

参考资料

**标准展望:**这些规则以 WCAG 2.2 AA 为目标。预计 WCAG 3.0 不会对表格无障碍要求作出重大更改。 关注:https://www.w3.org/TR/wcag-3.0/

评论

支持 Markdown