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

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



















