复杂图表可访问性需结构化数据表格+aria关联:用容器,/绘图,配语义化或替代内容,设role="img"、aria-describedby;多级图表拆分为独立,每表含与scope/headers;动态图表用aria-live="polite"同步摘要。

用 + 打底,但别只靠它
单纯套个 <figure></figure> 和 <figcaption></figcaption> 不等于可访问。屏幕阅读器会读出 <figcaption></figcaption>,但对复杂图表来说,这通常只是标题或一句话概括,远不足以替代视觉信息。比如柱状图里有 12 个月份、4 类产品、每根柱子还分上下两段——<figcaption></figcaption> 写不下,也读不全。
真正起作用的是组合:把 <figure></figure> 当容器,里面放图表(<svg></svg> 或 <canvas></canvas>),再配一个结构清晰的替代内容块(比如 <table> 或带层级的 <code><dl></dl>),并通过 ARIA 显式关联。
- 必须给图表元素加
role="img",否则部分屏幕阅读器可能忽略它 -
<figcaption></figcaption>只用于简明标题,例如“2023 年各季度销售额趋势”,不要塞细节 - 详细描述必须另起一块,用
aria-describedby指向它的id,且该块要放在同一<figure></figure>内或紧邻位置
复杂图表必须提供结构化数据表格
当图表含多维、分组、趋势对比等信息时,仅靠文本描述无法满足可访问性要求。WCAG 1.3.1(信息与关系)明确要求:语义结构不能仅靠视觉呈现。这意味着,你得把图表背后的数据还原成机器可解析、用户可导航的 HTML 表格。
关键不是“有没有表格”,而是“表格是否自洽”。常见错误是把原始 CSV 直接转成 <table>,却不设 <code>scope 或 headers,导致屏幕阅读器无法建立行列归属关系。
- 用
<thead> 定义列头,每列 <code><th> 加 <code>scope="col" - 行标题用
<th scope="row">,尤其在分组场景下(如“Q1”“Q2”“Q3”)必须明确其作用范围 <li>避免 <code>colspan/rowspan过度嵌套;若必须用,务必配合headers属性指向对应表头id - 表格应紧跟图表之后,或包裹在同一
<figure></figure>内,确保 DOM 顺序与逻辑顺序一致 - 每个
<section></section>必须有唯一id,且<h2></h2>用该id命名 <table> 内部必须含 <code><caption></caption>,例如<caption>Hospitalization cost breakdown</caption>- 不要把
<h2></h2>放在<table> 外又脱离 <code><section></section>,否则aria-labelledby失效 - 这种结构天然支持 NVDA/JAWS 的 H 键跳转,用户能直接定位到“Skilled nursing facility care”区块
- 摘要内容必须是语义化、可复用的句子,不能是“数据已更新”这种无效提示
- 如果图表同时提供表格替代,也要同步更新该表格的 DOM,保持两者数据一致
- 禁用
aria-hidden="true"在动态区域上——它会屏蔽aria-live效果
多级分组图表别硬塞进单表,拆成多个语义化
医疗费用表、保险责任分项、财务多维度对比——这类含“住院费用”“护理机构”“终身储备日”等逻辑区块的图表,强行压进一个 <table> 是可访问性灾难。屏幕阅读器逐单元格移动时,无法自动告知用户“你现在在‘住院费用’区块内”,<code>aria-describedby 只播一次,上下文就断了。
最可靠的做法是放弃单表幻想,按业务逻辑切分成独立 <table>,每个用 <code><section></section> 包裹,并通过 aria-labelledby 关联其 <h2></h2> 标题。
动态图表必须用 aria-live + 状态同步
图表随用户操作(筛选、悬停、时间轴拖动)实时更新时,仅刷新 SVG 或 Canvas 内容是不够的。屏幕阅读器不会主动感知 DOM 变化,除非你用 aria-live 明确告诉它:“这里有新数据,需要播报”。但乱用 aria-live 会导致重复、打断、信息过载。
正确做法是:每次数据变更后,更新一个隐藏的 <div aria-live="polite">,内容为当前视图的关键摘要(如“筛选后显示 Q3-Q4 数据,最高值为 180 万,出现在 10 月”),而不是整张表重刷。
<ul>
<li>
<code>aria-live="polite" 优于 "assertive",避免打断用户当前操作
scope + headers 配合,而不是仅靠视觉对齐。











