关于 accessibility
用于按所选 WCAG 2.2 等级和目标平台审查、修复 Flutter 应用无障碍问题的工作流技能,涵盖分阶段审计、代码级问题记录与范围明确的修复。
主要功能
- 01选择 WCAG 2.2 合规等级
- 02按平台检查 Flutter 应用
- 03覆盖七类无障碍审计
- 04提供严重性和 Dart 修复代码
- 05确定修复范围并编写回归测试
适用场景
- 01按指定 WCAG 等级审查 Flutter 组件
- 02针对目标平台检查无障碍情况
- 03生成含代码替换的可执行问题记录
- 04按优先级修复并添加回归测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ verygoodopensource/ vgv-ai-flutter-plugin --skill accessibility安装后在 Claude Code 中输入/accessibility确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
agents/openai.yamlreferences/audit-templates.mdexamples.mdtesting.mdwcag-criteria.mdwidget-mapping.mdreferences/platforms/android.mdios.mdlinux.mdmacos.mdweb.mdwindows.md详细介绍
无障碍
面向 WCAG 2.2 A、AA 和 AAA 一致性等级的 Flutter 无障碍审计与修复。本文件是工作流;附加资源中列出的参考文件会按需加载,以下每个阶段都会注明所需文件。
仅加载当前阶段指定的文件。
核心标准
所有无障碍工作都应遵循以下标准:
一致性等级 — 每次审计开始时,都要询问项目的目标 WCAG 2.2 一致性等级(A、AA、AA + 指定的 AAA,或 AAA)。绝不要默认是 AA。
平台选择 — 每次审计开始时,都要询问目标平台是哪一个或哪几个(iOS、Android、Web、macOS、Windows、Linux)。应用 references/platforms/ 中相应文件的平台规则。
图像语义(WCAG 1.1.1)— 每个 Image 都必须设置 semanticLabel,或用 Semantics(label:) 包裹。装饰性图像使用 excludeFromSemantics: true。
手势检测器(WCAG 2.1.1)— 绝不要将裸 GestureDetector 用作点击目标。使用 InkWell、ElevatedButton、TextButton 或 IconButton。GestureDetector 仅支持指针操作,无法通过键盘或开关访问。
目标尺寸(WCAG 2.5.8)— AA 级的目标尺寸最小为 24x24 CSS px(≈ 24 dp)。VGV 建议的最小尺寸为 48x48 dp。AA 级下,24 dp 到 48 dp 之间的问题标记为 VGV 风格的问题;AAA 级下,则依据 2.5.5(44 dp)标记为 WCAG 问题。
拖动替代方式(WCAG 2.5.7)— 所有基于拖动的功能都必须在同一屏幕上提供非拖动的替代方式。滑块需要键盘或步进器替代方式。Dismissible 需要明确的删除按钮。ReorderableListView 需要上移/下移或“移动到”控件。
焦点不被遮挡(WCAG 2.4.11)— 粘性标题栏、Snackbar、底部弹层、常驻 FAB 或覆盖层不得完全遮挡获得焦点的小部件。使用 Scrollable.ensureVisible 和 Scaffold.resizeToAvoidBottomInset: true。
颜色区分(WCAG 1.4.1)— 绝不能仅用颜色区分内容。始终搭配标签、图标或形状。
动画与动态效果(WCAG 2.3.3)— 所有动画都必须遵循 MediaQuery.disableAnimations。每个 AnimationController、AnimatedContainer、Hero 转场和 PageRouteBuilder 转场都必须受此标志控制。
图标按钮(WCAG 4.1.2)— 仅含图标的按钮必须有 Tooltip 或 Semantics(label:)。屏幕阅读器没有其他方式传达其用途。
排除语义(WCAG 1.1.1)— 绝不要对非装饰性内容使用 ExcludeSemantics。它会从语义树中移除整个子树,因此其中的任何按钮或其他控件都会从 TalkBack 和 VoiceOver 中消失,屏幕阅读器用户也无法触发其操作。拒绝将可操作内容包裹在其中的请求时,要说明这一后果。若要避免屏幕阅读器停留在每个子项上,只对静态标签和值这对内容使用 MergeSemantics,并将交互式同级项留在其外部,以便它们仍可独立获得焦点。
文本容器(WCAG 1.4.4)— 固定高度的容器不得包裹 Text。使用 minHeight 约束。固定高度在 Android 上会于字体缩放 1.5x 时裁切文本;在 iOS 上更早,因为“更大辅助功能字体”最高约为 3.1x。
对比度(WCAG 1.4.3)— 所有文本和 UI 组件都必须达到所选 WCAG 等级要求的对比度。参见 references/wcag-criteria.md。
Cupertino 语义(WCAG 4.1.2)— Cupertino 小部件(CupertinoSwitch、CupertinoSlider、CupertinoSegmentedControl、CupertinoButton)自带的语义默认值弱于对应的 Material 小部件。始终用 Semantics(label:, value:, button:) 包裹它们。
自动填充提示(WCAG 1.3.5)— 每个用于收集结构化个人数据(电子邮件、用户名、密码、姓名、地址、电话、oneTimeCode)的 TextField 都必须声明 autofillHints。AA 级下,1.3.5 要求必须如此;同时,这也是 A 级 3.3.7“重复输入”的基础。
发现格式 — 每项审计发现都必须附带 WCAG 标准编号及其名称(2.5.8 Target Size (Minimum),不得只写 2.5.8)、恰好为 CRITICAL、MAJOR 或 MINOR 之一的大写严重性等级,以及以修改前和修改后的 Dart 代码呈现的修复方案。不得将严重性评为 High/Medium/Low 或 Serious/Moderate/Minor。不得提交修复方案只有文字说明的发现。
异步播报(WCAG 4.1.3)— 每次异步的用户可见状态变更都必须通过 Semantics(liveRegion: true) 或 SemanticsService.announce 播报。
工作流
每次无障碍工作都必须依次完成四个阶段。不得跳过阶段 1 或阶段 2。
跨运行环境说明。 以下阶段 1、2 和 4 会用到
AskUserQuestion。如果运行环境没有该工具,请调用该环境提供的等效提问工具;如果没有,则用带编号的纯文本提出相同问题。无论采用哪种方式,都必须等待用户回复后再继续。
阶段 1:符合性等级选择
使用 AskUserQuestion 提问:
question: "Which WCAG 2.2 conformance level are you targeting?"
header: "WCAG level"
options:
- label: "A"
description: "Removes the most critical barriers. Includes the new 2.2 criteria 3.2.6 Consistent Help and 3.3.7 Redundant Entry."
- label: "AA"
description: "Standard most regulators require. Adds contrast, resize text, focus visible, plus the four new 2.2 AA criteria: 2.4.11, 2.5.7, 2.5.8, 3.3.8."
- label: "AA + selected AAA"
description: "AA across the app, plus specific AAA criteria scoped to flagged flows. Common: 1.4.6 enhanced contrast, 2.2.3 no timing, 2.4.13 focus appearance."
- label: "AAA"
description: "Full AAA. 7:1 contrast, no timing, no exceptions to keyboard. Rare for whole products."
如果用户选择“AA + selected AAA”,请接着用自由文本请求用户提供希望纳入的 AAA 标准编号(例如,“1.4.6, 2.2.3, 2.4.13”)。
**结果:**记录所选等级。所有审计检查、标准引用和修复建议都只适用该等级(以及所有较低等级)的规则,和用户选择纳入的任何 AAA 标准。
阶段 2:平台选择
使用 AskUserQuestion(如果支持则使用多选,否则提出一个问题并让用户以逗号分隔回复)提问:
question: "Which platforms is this app targeting? Select all that apply."
header: "Platforms"
options:
- label: "iOS"
description: "VoiceOver, Switch Control, Dynamic Type up to 3.1x, Voice Control, Bold Text, Reduce Motion, Reduce Transparency."
- label: "Android"
description: "TalkBack, Switch Access, font scale up to 2x, Voice Access, color inversion."
- label: "Web"
description: "Flutter Web rendered to a Semantics-mapped DOM. NVDA + Chrome, JAWS + Chrome, VoiceOver + Safari."
- label: "macOS"
description: "VoiceOver, Full Keyboard Access, Reduce Motion, Increase Contrast."
- label: "Windows / Linux desktop"
description: "Narrator, NVDA, JAWS (Windows), Orca (Linux), Windows High Contrast Mode."
**结果:**记录所选平台。加载 references/platforms/ 中匹配的文件(例如,iOS 对应 references/platforms/ios.md,Android 对应 references/platforms/android.md)。只加载所选平台对应的文件,不要加载不必要的文件。
阶段 3:按级别和平台进行审计
针对每个选定的平台,按以下七个类别依次审计提供的文件或组件:
-
语义和屏幕阅读器 — 标签、角色、实时区域、合并/排除是否正确、Cupertino 语义缺口,以及 TalkBack 和 VoiceOver 下的阅读顺序。
-
触控目标和拖动替代方案 — WCAG 2.2 2.5.8 AA 级别最低要求(24 CSS px)、2.5.5 AAA 级别增强要求(44 CSS px)、VGV 建议的 48 dp,以及 2.5.7 拖动替代方案。
-
焦点和键盘导航 — 可操作性、遍历顺序、对话框焦点捕获、焦点指示器,以及 2.4.11 / 2.4.12 焦点不被遮挡。
-
颜色对比度 — 文本和 UI 组件的比例需达到所选级别的阈值(见下表)。
-
文本缩放 — 不使用固定高度的文本容器,不限制文本缩放。Android 和 Web 的模拟上限为 2 倍,iOS 为 3 倍。
-
动画和运动 — 在
AnimationController、Hero、AnimatedContainer、PageRouteBuilder中使用disableAnimations进行控制。AA 级别下,内容闪烁频率不得超过 3 Hz;AAA 级别下不得闪烁。 -
表单、身份验证和帮助 —
autofillHints(1.3.5、3.3.7)、无障碍身份验证(3.3.8 / 3.3.9)、帮助机制的位置保持一致(3.2.6)。
仅应用所选级别下生效的标准(以及选择加入的 AAA 标准),并且仅应用于所选平台。
将每条发现都写在此区块中,不得省略任何一行:
### 1. Close button is a 16x16 GestureDetector
- **File:** lib/order/order_summary.dart ~L26
- **WCAG:** 2.1.1 Keyboard (Level A, WCAG 2.0)
- **Platform(s):** iOS, Android
- **Severity:** CRITICAL
- **Issue:** `GestureDetector` is pointer-only, so the close action is unreachable via VoiceOver, TalkBack, Switch Control, and Switch Access. Expected: a focusable, activatable button with an accessible name.
- **Fix:**
```dart
// Before
SizedBox(
width: 16,
height: 16,
child: GestureDetector(
onTap: onClose,
child: const Icon(Icons.close, size: 16),
),
)
```
```dart
// After
IconButton(
onPressed: onClose,
tooltip: 'Close order summary',
iconSize: 24,
constraints: const BoxConstraints(minWidth: 48, minHeight: 48),
icon: const Icon(Icons.close),
)
```
报告中每条发现都必须遵守以下规则,包括 MINOR 级别的发现:
- 标准。 将编号和标准名称写在一起,随后注明级别和 WCAG 版本:
2.5.8 Target Size (Minimum) (Level AA, WCAG 2.2)。仅写编号的发现不完整。 - 严重程度。 必须且只能从
references/audit-templates.md中的严重程度指南选择 CRITICAL、MAJOR、MINOR 之一。平台间存在差异时,保留这三个标签并注明平台:CRITICAL (iOS, Android), MAJOR (Web)。 - 修复。 每条发现都必须包含 Before 和 After 代码块,展示真实的 Dart 代码,并在该发现下并排呈现。仅提供文字建议不算修复,仅提供 After 代码块也不算修复——读者必须看到被替换的确切代码。报告末尾集中提供一个修正后的组件,不能满足以上发现的要求。
- 范围。 如果检查发现的问题无法仅凭代码确认(例如,缺少颜色信息无法判断对比度,或没有运行应用无法判断阅读顺序),应将其列在 Out of Scope 下,而不是作为发现。发现必须有依据、可评级且可修复。
结果: 完成全部七个类别后,使用 references/audit-templates.md 中的模板生成审计报告。选择与阶段 1 相符的级别对应的已通过检查列表。
阶段 4:选择修复范围
提交报告后,使用 AskUserQuestion:
question: "The audit is complete. How would you like to proceed with fixes?"
header: "Fix scope"
options:
- label: "All issues"
description: "Fix every CRITICAL, MAJOR, and MINOR finding"
- label: "Critical + Major only"
description: "Fix blockers and significant barriers; skip MINOR polish items"
- label: "Critical only"
description: "Fix only what blocks assistive technology users entirely"
- label: "Specific findings"
description: "List the finding numbers you want fixed"
结果: 严格按用户选择应用修复。应用修复后,确认:“已修复 [N] 条发现([severities])。剩余 [N] 条仍未解决。”
为每项修复编写测试,防止回归。如果不清楚特定组件的正确实现方式,请参阅 references/testing.md 中涵盖全部七个审计类别的测试套件,以及 references/widget-mapping.md。
WCAG 2.2 级别标准参考
references/wcag-criteria.md 中提供了各级别的标准表,并列出每项标准对应的 Flutter 检查。在阶段 3 加载该文档。
常见反模式速查
完整代码示例及修正版本见 references/examples.md。以下是此技能最常标记的模式。
语义(WCAG 1.1.1、4.1.2)
// WRONG: empty label, no label, ExcludeSemantics over actionable content removes the button
// from the semantics tree entirely, so the action is unreachable for screen reader users
Image.asset('assets/warning.png', semanticLabel: '')
Image.asset('assets/chart.png')
ExcludeSemantics(child: ElevatedButton(onPressed: _submit, child: const Text('Submit')))
// WRONG: Cupertino without semantic wrapper
CupertinoSwitch(value: _enabled, onChanged: _onChanged)
// WRONG: MergeSemantics around an interactive child folds the button's role away
MergeSemantics(
child: Row(children: [const Text('Item'), IconButton(onPressed: _delete, icon: ...)]),
)
触控目标和拖动(WCAG 2.5.8、2.5.7)
// WRONG: 16x16 target, below WCAG 2.2 2.5.8 AA (24 dp)
SizedBox(width: 16, height: 16, child: GestureDetector(onTap: _onTap, child: const Icon(Icons.close, size: 16)))
// WRONG: Dismissible with no non-drag alternative (WCAG 2.2 2.5.7)
Dismissible(key: ValueKey(item.id), onDismissed: (_) => _delete(item), child: ListTile(title: Text(item.name)))
焦点(WCAG 2.1.1、2.4.11)
// WRONG: GestureDetector is not keyboard-accessible
GestureDetector(onTap: _onTap, child: const Text('Click me'))
// WRONG: bottom bar covers focused TextField (WCAG 2.2 2.4.11)
Scaffold(
resizeToAvoidBottomInset: false,
bottomNavigationBar: const BottomAppBar(child: ...),
body: ListView(children: [..., TextField(focusNode: _last), ...]),
)
文本缩放与动态效果(WCAG 1.4.4、2.3.3)
// WRONG: clips text at 1.5x font scale
SizedBox(height: 48, child: Text('Status: Ready'))
// WRONG: animation always plays
AnimatedContainer(duration: const Duration(milliseconds: 500), color: ..., child: child)
修正后的代码片段、完整类(AccessibleTapTarget、AccessibleSlider、AccessibleReorderableList、AccessiblePageRoute、AccessibleHero)以及 Cupertino 语义包装器,请参阅 references/examples.md。
其他资源
references/wcag-criteria.md— 按级别划分的 WCAG 2.2 标准表,每项均附有相应的 Flutter 检查项。references/audit-templates.md— 严重性指南、报告模板、按级别划分的通过检查项列表、跨平台严重性表。references/examples.md— 按类别整理的完整 Flutter 组件类,包含所有 WCAG 2.2 模式。references/widget-mapping.md— 常见 Flutter 组件对应要求的快速参考。references/testing.md— 涵盖全部七类审计的完整无障碍测试套件示例。references/platforms/ios.md、android.md、web.md、macos.md、windows.md、linux.md— 各平台的 WCAG 2.2 检查项和 Flutter 特有注意事项。
官方参考资料: