PlantUML SVG 无障碍:屏幕阅读器、WCAG、配色对比度

puml.online

PlantUML 默认 SVG 对视障用户不友好——没有 alt 文本、屏幕阅读器读不出来、配色对色盲对比度不够。合规场景(政府、教育、金融)要求 WCAG 2.1 AA。这篇是怎么让 PlantUML 图可访问。

为什么 SVG 要无障碍

3 个用户群体:

  1. 视障——用屏幕阅读器(NVDA / JAWS / VoiceOver)
  2. 运动障碍——用键盘或开关设备导航
  3. 色盲——需要颜色 + 形状/文字双重编码

PlantUML 默认 SVG——所有 element 没有 <title> <desc> role 属性,屏幕阅读器读到「Image, no description」就跳过

WCAG 2.1 AA 三条硬指标

准则 要求 PlantUML 默认
1.1.1 Non-text content 图必须有 alt 或 longdesc ✗ 无 alt
1.4.3 Contrast 文字背景对比度 ≥ 4.5:1 ✓ 默认黑底白通常满足
2.4.7 Focus visible 键盘焦点可见 ✗ 无 tabindex
4.1.2 Name, role, value 控件有可访问名 ✗ 无 aria-label

修法 1:titledesc 指令

PlantUML 1.2020+ 支持内嵌 <title><desc>:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@startuml login_flow
title "User Login Flow"

actor User
participant "Web App" as Web
participant "Auth Service" as Auth
database "Database" as DB

User -> Web : Enter credentials
Web -> Auth : POST /login
Auth -> DB : SELECT user WHERE email = ?
DB --> Auth : user record
Auth --> Web : 200 OK + JWT
Web --> User : Redirect to dashboard

@enduml

title 生成的 SVG 自带 <title> 元素——屏幕阅读器读「User Login Flow」作为图标题

但 PlantUUM 的 title 只生成 <title>不生成 <desc>——更详细描述需要 post-process。

修法 2:caption 指令(1.2023+)

1
2
3
4
5
6
7
8
9
10
11
@startuml
caption "Detailed flow showing user authentication with JWT token generation. Steps: 1) User enters credentials, 2) Web forwards to Auth Service, 3) Auth checks Database, 4) JWT returned."

title "User Login Flow"

actor User
participant "Auth Service" as Auth
User -> Auth : credentials
Auth --> User : JWT

@enduml

caption 生成的 SVG 有 <desc> 元素——屏幕阅读器朗读详细描述

修法 3:Post-process 注入完整 a11y 属性

PlantUML 不支持的内置 a11y,用 Python/Node post-process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# make_accessible.py
import sys, pathlib, re

svg = pathlib.Path(sys.argv[1]).read_text()
diagram_name = sys.argv[2] or "PlantUML Diagram"
diagram_desc = sys.argv[3] or "Architecture diagram"

# 1. 注入 role 和 aria-label 到 <svg> 根元素
svg = re.sub(
r'<svg([^>]*)>',
f'<svg\\1 role="img" aria-label="{diagram_name}" aria-describedby="diagram-desc-{id(diagram_name)}">',
svg,
count=1,
)

# 2. 注入 <title> 元素(如果 PlantUML 没生成)
if '<title>' not in svg:
title_elem = f'<title>{diagram_name}</title>'
svg = svg.replace('<svg', f'<svg', 1)
svg = svg.replace('>', f'>{title_elem}', 1)

# 3. 注入 <desc> 元素
desc_elem = f'<desc id="diagram-desc-{id(diagram_name)}">{diagram_desc}</desc>'
if '<desc' not in svg:
svg = svg.replace('</title>', f'</title>{desc_elem}', 1)

# 4. 给每个节点 <g> 元素加 <title> 子元素
# PlantUML SVG 节点通常带 id="<alias>" 或 data-node-id
def add_title_to_node(match):
full = match.group(0)
node_id = match.group(1)
label = match.group(2)
return full.replace(
f'id="{node_id}"',
f'id="{node_id}" aria-label="{label}" tabindex="0" role="button"',
1,
)

# PlantUML 节点带 data-node-id 属性
svg = re.sub(
r'<g[^>]*data-node-id="(\w+)"[^>]*>(.*?)(?=</g>)',
lambda m: m.group(0).replace(
f'data-node-id="{m.group(1)}"',
f'data-node-id="{m.group(1)}" tabindex="0" role="button" aria-label="Node {m.group(1)}"',
1,
),
svg,
)

pathlib.Path(sys.argv[1]).write_text(svg)
1
python make_accessible.py architecture.svg "User Authentication Architecture" "Diagram showing user login flow with JWT token generation"

生成的 SVG 现在:

  • <svg role="img" aria-label="..." aria-describedby="...">
  • 内嵌 <title> <desc>
  • 每个节点 <g tabindex="0" role="button" aria-label="...">

屏幕阅读器现在能朗读每个节点的描述,键盘 Tab 键可以聚焦每个节点。

修法 4:对比度优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@startuml
skinparam defaultTextColor #000000
skinparam defaultFontSize 14
skinparam backgroundColor #FFFFFF

skinparam ArrowColor #000000
skinparam ComponentBorderColor #000000
skinparam NoteBackgroundColor #FFFFCC
skinparam NoteBorderColor #000000
skinparam NoteFontColor #000000

component "Auth Service" as auth
component "User Service" as user

user --> auth : HTTP
@enduml

WCAG AA 要求:

  • 文字 vs 背景对比度 ≥ 4.5:1
  • 大字体(≥18pt 或粗 14pt)对比度 ≥ 3:1

PlantUML 默认配色——黑文字白底 #000000 vs #FFFFFF = 21:1(完美)。但浅灰背景 + 深灰文字 = 失败。

WCAG AAA 严格场景对比度 ≥ 7:1——文字颜色必须纯黑或近黑。

修法 5:色盲适配

8% 男性 / 0.5% 女性是色盲——红绿色盲最常见。PlantUML 默认主题红绿区分:

1
2
3
4
skinparam sequence {
LifeLineBorderColor red
LifeLineBackgroundColor #FFEEEE
}

红绿色盲看不到——需要形状 + 文字 + 颜色三重编码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
skinparam Participant {
BackgroundColor #E0E0E0
BorderColor #000000
FontColor #000000
FontStyle bold
}
skinparam Actor {
BackgroundColor #FFE0B2
BorderColor #000000
}

actor User <<Human>>
participant "Auth" <<Service>> as auth
database "DB" <<Storage>> as db

User -> auth : ① login
auth -> db : ② query
db --> auth : ③ result
auth --> User : ④ JWT
@enduml

步骤编号 ①②③④——色盲也能看清顺序。

修法 6:键盘导航 post-process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// keyboard-nav.js
document.querySelectorAll('svg [data-node-id]').forEach(node => {
node.setAttribute('tabindex', '0');
node.setAttribute('role', 'button');

node.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
node.dispatchEvent(new MouseEvent('click'));
}
});

node.addEventListener('focus', () => {
node.style.outline = '3px solid #FF6B00';
});
node.addEventListener('blur', () => {
node.style.outline = '';
});
});

Tab 键聚焦节点 → 看到橙色轮廓 → Enter 键触发 click——完整键盘可达。

验证工具

WAVE (Web Accessibility Evaluation Tool)

浏览器插件:https://wave.webaim.org/extension/

打开含 SVG 的页面 → WAVE 报告 SVG 的 a11y 问题:

  • Missing alt text
  • Empty link
  • Missing form label

axe DevTools

DevTools 插件:运行 axe → 列出所有 a11y 违规。

Lighthouse

Chrome DevTools → Lighthouse → Accessibility 评分。目标 100/100

手测

  1. macOS VoiceOver:Cmd+F5 启用 → Tab 键浏览 → 应能听到 SVG 节点描述
  2. Windows NVDA:免费屏幕阅读器 → 浏览页面
  3. 键盘 only:拔掉鼠标 → Tab 键应能聚焦所有节点

实战:合规场景的图

政府 / 医疗 / 教育 / 金融网站要求 WCAG 2.1 AA。PlantUML 图要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@startuml
title "用户登录流程"
caption "图示用户从输入凭证到获取 JWT token 的完整流程,共 5 个步骤。涉及 Web 应用、认证服务、数据库三个组件。"

skinparam defaultTextColor #000000
skinparam backgroundColor #FFFFFF
skinparam ArrowColor #000000
skinparam ParticipantPadding 20
skinparam BoxPadding 15

actor "用户" as User
participant "Web 应用" as Web
participant "认证服务" as Auth
database "数据库" as DB

User -> Web : ① 输入凭证
Web -> Auth : ② POST /login
Auth -> DB : ③ 查询用户
DB --> Auth : ④ 返回用户记录
Auth --> Web : ⑤ 返回 JWT
Web --> User : 重定向到首页

@enduml

配套:

  • SVG post-process 加 role="img" aria-label
  • 节点加 tabindex aria-label
  • 页面有文字版「图描述」段落(<details><summary>文字描述</summary>...</details>)
  • 跳过导航链接允许跳过 SVG

关键 a11y HTML 模式

1
2
3
4
5
6
7
8
9
10
11
12
<figure role="figure" aria-labelledby="diagram-title" aria-describedby="diagram-desc">
<img src="architecture.svg" alt="">
<figcaption>
<h3 id="diagram-title">用户认证架构</h3>
<p id="diagram-desc">图示系统认证流程...</p>
</figcaption>
</figure>

<details>
<summary>📝 文字版描述(适合屏幕阅读器)</summary>
<p>用户从浏览器进入登录页面,Web 应用接收凭证后转发到认证服务...</p>
</details>

<img alt="">——空 alt 让屏幕阅读器跳过图本身,figcaption 提供描述

实战踩坑

  • PlantUML title 不渲染 longdesc——只生成 <title>,详细描述要么用 caption,要么 post-process 注入 <desc>
  • <svg role="img"> 必须配合 alt——WAVE 会报「SVG missing alternative content」,<svg> 自身加 aria-label
  • 焦点轮廓被 CSS 覆盖——很多主题设 *:focus { outline: none },导致键盘用户看不到聚焦。禁止全局 outline: none,只在 button 之类显式设置
  • 色盲模式(Windows 高对比度)——SVG 颜色被 OS 强制替换,可能不可读。确保不依赖颜色单独区分
  • 打印样式——彩色 SVG 打印成黑白,色盲友好但失去颜色信息。prefers-color-scheme 媒体查询区分:
    1
    2
    3
    4
    @media print {
    svg .status-ok { fill: #000 !important; }
    svg .status-down { fill: #888 !important; }
    }

决策树

1
2
3
4
5
6
7
8
9
需要 a11y 吗?
├─ 不需要(内部 demo)→ 默认 SVG
├─ 公开页面但非合规 → post-process 加 title/desc/role
├─ WCAG 2.1 AA → 完整 post-process + 焦点 + 对比度
└─ WCAG 2.1 AAA(政府/医疗) → 严格对比度 + 文字版描述 + 测试

图有信息意义吗?
├─ 是 → 必须 a11y
└─ 否(纯装饰) → alt="" 跳过

最小 a11y 修法:给 <svg>role="img" aria-label="...",post-process 注入 <title> <desc>,屏幕阅读器就能读图了——30 行 Python 解决 80% 的 a11y 问题。

  • 标题: PlantUML SVG 无障碍:屏幕阅读器、WCAG、配色对比度
  • 作者: puml.online
  • 创建于 : 2026-07-30 17:20:00
  • 更新于 : 2026-08-14 21:34:29
  • 链接: https://puml.online/blog/plantuml-svg-accessibility/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。