跳到主要内容

SVG无障碍

inline SVG 可以进无障碍树:每个图形节点都能有名称、角色、焦点。一旦把同一张图做成 <img src="*.svg"> 或背景图,树就没了,只剩一张图。可交互图(热区、流程图、白板)必须 inline,不要 img

1. title / desc 与名称

<svg role="img" aria-labelledby="title-1 desc-1">
<title id="title-1">2026 Q3 销售额</title>
<desc id="desc-1">华东最高,东北最低。</desc>
<!-- 几何 -->
</svg>

&lt;title> 第一子节点是原生 tooltip 和可访问名的常见来源,但三引擎对「自动当 accessible name」并不一致。生产显式 aria-labelledby 指向 title(和需要时的 desc),不要赌悬停提示等于读屏。

装饰性图标:宿主按钮已经有 accessible name 时,内联 SVG aria-hidden="true",避免双读。

export function labelGraphic(
el: SVGGraphicsElement,
name: string,
detail?: string,
): void {
const title = document.createElementNS("http://www.w3.org/2000/svg", "title");
title.textContent = name;
el.prepend(title);
el.setAttribute("aria-label", name);
if (detail) {
el.setAttribute("aria-description", detail);
}
}

复杂图还要同等文本:旁边 HTML 表或 aria-describedby。只靠一句 label 不够。

2. 角色:静态 vs 可交互

形态角色不要
静态图表/插画role="img" + 名称再给每个叶子 img
一组可点热区热区 role="button" 或真实 HTML overlay根再 role="img"(子树被压成一张图)
应用画布谨慎 role="application",必须自备全套键盘默认给所有编辑器扣 application

role="img" 会把后代从可访问树上折叠。可点的城市、可点的柱子,不要包在 img 里。

不要:<img src="map.svg" usemap> 当现代方案。要热区就 inline,或 HTML overlay 按钮用 getScreenCTM 对齐。

3. 键盘

可聚焦:tabindex="0"(或按顺序的正整数,尽量少用)。SVG 元素默认往往 不进 Tab 序。焦点可见:CSS :focus / :focus-visible 画描边,vector-effect: non-scaling-stroke 避免缩小时环消失。

export function enableGraphicFocus(el: SVGGraphicsElement): void {
el.setAttribute("tabindex", "0");
el.setAttribute("role", "button");
el.addEventListener("keydown", (event: KeyboardEvent) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
el.dispatchEvent(new Event("activate"));
}
});
}

方向键遍历节点、Delete 删除、Esc 清选:和指针命中改 同一份 selectedId。焦点在 A、鼠标选中 B 是缺陷。列表型侧栏可以当键盘主入口,svg tabindex="-1" 只鼠标,减少 application 角色。

触控热区在屏幕空间 ≥ 44 CSS 像素;细线用透明扩大命中,见 指针事件

prefers-reduced-motion:关掉相机动画和闪烁选区。

4. 适用 / 不适用

适用:任何可点、可选、承载信息的 inline SVG。
不适用:纯装饰(aria-hidden);已是按钮内容的图标(跟按钮走);把编辑器假扮成一张 img 交差。

WCAG 对非文本内容、键盘、焦点可见、名称角色值的要求,SVG 不会豁免。&lt;canvas> 必须平行 DOM;SVG 没借口却做成 img,是主动扔掉已有的树。

5. 失败形态

症状原因
读屏只说「图像」可交互图画成了 <img>,或根 role="img" 吞掉热区
有 title 但读屏无名称aria-labelledby / aria-label,引擎没把 title 当名字
Tab 进不去tabindex
焦点环缩没了描边在用户空间,没 non-scaling-stroke
键盘选中和鼠标不是同一个两份 focus state
按钮读两遍图标没 aria-hidden,title 又念一遍

权威资料

核对日期:2026-08-26