mcpField
A
accessibility

accessibility

github.com/microsoft设计与 UI194.4k3.2k 次安装今天更新
代码仓库

关于 accessibility

用于开发 VS Code 交互界面的无障碍技能,为贡献者提供键盘操作、屏幕阅读器支持、无障碍视图和动态播报的要求与实现指引。

主要功能

  1. 01添加无障碍帮助对话框
  2. 02以纯文本呈现视觉内容
  3. 03配置无障碍提示设置
  4. 04提供声音与语音信号
  5. 05使用 ARIA 警报和状态消息
  6. 06支持键盘导航

适用场景

  1. 01构建新的 VS Code 面板或控件
  2. 02改进现有交互的无障碍支持
  3. 03让屏幕阅读器读取视觉内容
  4. 04无障碍播报错误和进度

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/vscode --skill accessibility

安装后在 Claude Code 中输入/accessibility确认已加载。

详细介绍

何时使用此技能

对于任何引入或更改交互式 UI 的 VS Code 功能开发,都应使用此技能。新功能和贡献默认使用此技能,即使请求未明确提及无障碍性。

触发示例: -“添加新功能” -“实现新的面板/视图/小组件” -“添加新命令或工作流” -“在 workbench/editor/extensions 中添加新贡献” -“更新现有 UI 交互”

不要仅仅因为提示中没有提及无障碍性就跳过此技能。

在 VS Code 中添加新的交互式 UI 界面时——例如面板、视图、小组件、编辑器叠加层、对话框,或任何用户可以交互且可聚焦的丰富组件——如果该功能尚未提供以下无障碍组件,则必须添加:

  1. 无障碍帮助对话框——当功能获得焦点时,可通过无障碍帮助快捷键打开。
  2. 无障碍视图——以纯文本只读编辑器的形式呈现功能内容,供屏幕阅读器用户使用(当功能显示非简单的视觉内容时)。
  3. 无障碍详细程度设置——布尔设置,用于控制是否播报“打开无障碍帮助”提示。

具备全部三项的现有功能包括:终端、聊天面板、笔记本、差异编辑器、内联补全、评论、调试 REPL、悬停和通知。仅提供帮助对话框(没有无障碍视图)的功能包括:查找小组件、源代码管理输入框、键盘快捷方式编辑器、问题面板和入门教程。

以下第 4–7 节(信号、ARIA 播报、键盘导航、ARIA 标签)适用于范围更广的任何 UI 更改,包括对现有功能的修改。

更新现有功能时(例如添加新命令、键盘快捷键或交互能力),还必须更新该功能现有的无障碍帮助对话框(provideContent()),以说明新增功能。屏幕阅读器用户依赖帮助对话框来了解可用操作。


1. 无障碍帮助对话框

无障碍帮助对话框会告知用户该功能的用途、可用的键盘快捷键,以及如何通过屏幕阅读器与其交互。

步骤
  1. 创建一个实现 IAccessibleViewImplementation 的类,并设置 type = AccessibleViewType.Help。

    • 设置 priority(值越大,在多个提供程序匹配时越先显示)。
    • 将 when 设置为与功能处于焦点状态时匹配的 ContextKeyExpression。
    • getProvider(accessor) 返回一个 AccessibleContentProvider。
  2. 创建一个实现 IAccessibleViewContentProvider 的内容提供程序类。

    • id — 在 src/vs/platform/accessibility/browser/accessibleView.ts 中的 AccessibleViewProviderId 枚举里新增一项。
    • verbositySettingKey — 引用新增的 AccessibilityVerbositySettingId 项(参见 §3)。
    • options — { type: AccessibleViewType.Help }。
    • provideContent() — 返回本地化的多行帮助文本。
  3. 实现 onClose(),将焦点恢复到帮助对话框打开前获得焦点的元素。这可确保键盘用户和屏幕阅读器用户返回之前的上下文。

  4. 在功能的 *.contribution.ts 文件中注册该实现:

    AccessibleViewRegistry.register(new MyFeatureAccessibilityHelp());
    
示例框架

最简单的做法是在 getProvider() 中直接返回 AccessibleContentProvider。这是代码库中最常见的模式(chat、inline chat、quick chat 等都采用此模式):

import { AccessibleViewType, AccessibleContentProvider, AccessibleViewProviderId } from '../../../../platform/accessibility/browser/accessibleView.js';
import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';
import { AccessibilityVerbositySettingId } from '../../../../platform/accessibility/common/accessibilityConfiguration.js';

export class MyFeatureAccessibilityHelp implements IAccessibleViewImplementation {
	readonly priority = 100;
	readonly name = 'my-feature';
	readonly type = AccessibleViewType.Help;
	readonly when = MyFeatureContextKeys.isFocused;

	getProvider(accessor: ServicesAccessor) {
		const helpText = [
			localize('myFeature.help.overview', "You are in My Feature. …"),
			localize('myFeature.help.key1', "- {0}: Do something", '<keybinding:myFeature.doSomething>'),
		].join('\n');
		return new AccessibleContentProvider(
			AccessibleViewProviderId.MyFeature,
			{ type: AccessibleViewType.Help },
			() => helpText,
			() => { /* onClose — refocus whatever was focused before */ },
			AccessibilityVerbositySettingId.MyFeature,
		);
	}
}

或者,如果提供程序需要注入服务或必须跟踪状态(例如,保存先前获得焦点的元素引用),则可以创建一个扩展 Disposable 并实现 IAccessibleViewContentProvider 的自定义类,然后通过 IInstantiationService 实例化该类(示例请参阅 CommentsAccessibilityHelpProvider):

class MyFeatureAccessibilityHelpProvider extends Disposable implements IAccessibleViewContentProvider {
	readonly id = AccessibleViewProviderId.MyFeature;
	readonly verbositySettingKey = AccessibilityVerbositySettingId.MyFeature;
	readonly options: IAccessibleViewOptions = { type: AccessibleViewType.Help };

	provideContent(): string { /* … */ }
	onClose(): void { /* … */ }
}

// In getProvider():
getProvider(accessor: ServicesAccessor) {
	return accessor.get(IInstantiationService).createInstance(MyFeatureAccessibilityHelpProvider);
}

2. 无障碍视图

无障碍视图会在只读编辑器中以纯文本形式呈现功能的视觉内容。当功能会渲染屏幕阅读器无法直接读取的富内容或视觉内容时(例如:聊天回复、悬停提示、通知、终端输出、内联补全),必须提供无障碍视图。

如果功能完全通过键盘操作,并使用原生文本输入/输出(例如:简单的输入框),则不需要无障碍视图,只需提供无障碍帮助对话框。

步骤
  1. 创建一个实现 IAccessibleViewImplementation 的类,并设置 type = AccessibleViewType.View。
  2. 创建一个内容提供器,形式类似帮助对话框,但需满足以下要求:
    • options — { type: AccessibleViewType.View },可选设置用于语法高亮的 language。
    • provideContent() — 以纯文本形式返回功能当前的内容。
    • 可选实现 provideNextContent() / providePreviousContent(),以便逐项导航。
    • 实现 onClose(),将焦点恢复到打开无障碍视图之前的焦点位置。
    • 可选提供 actions,用于让用户在无障碍视图中执行操作。
  3. 注册,与帮助对话框一起注册:
    AccessibleViewRegistry.register(new MyFeatureAccessibleView());
    
示例框架
export class MyFeatureAccessibleView implements IAccessibleViewImplementation {
	readonly priority = 100;
	readonly name = 'my-feature';
	readonly type = AccessibleViewType.View;
	readonly when = MyFeatureContextKeys.isFocused;

	getProvider(accessor: ServicesAccessor) {
		// Retrieve services, build content from the feature's current state
		const content = getMyFeatureContent();
		if (!content) {
			return undefined;
		}
		return new AccessibleContentProvider(
			AccessibleViewProviderId.MyFeature,
			{ type: AccessibleViewType.View },
			() => content,
			() => { /* onClose — refocus whatever was focused before the accessible view opened */ },
			AccessibilityVerbositySettingId.MyFeature,
		);
	}
}

3. 无障碍详细程度设置

详细程度设置用于控制功能获得焦点时是否播报提示,例如“按 Alt+F1 获取无障碍帮助”。已熟悉快捷键的用户可以禁用此提示。

步骤
  1. 在 src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts 的 AccessibilityVerbositySettingId 中添加一项:

    export const enum AccessibilityVerbositySettingId {
        // … existing entries …
        MyFeature = 'accessibility.verbosity.myFeature'
    }
    
  2. 在同一文件的 configuration.properties 对象中注册配置属性:

    [AccessibilityVerbositySettingId.MyFeature]: {
        description: localize('verbosity.myFeature.description',
            'Provide information about how to access the My Feature accessibility help menu when My Feature is focused.'),
        ...baseVerbosityProperty
    },
    

    baseVerbosityProperty 会为其设置 type: 'boolean'、default: true 和 tags: ['accessibility']。

  3. 在帮助对话框提供器(verbositySettingKey)和无障碍视图提供器中引用此设置键,以便运行时检查是否显示提示。


4. 无障碍信号(声音和播报)

无障碍信号会为视觉上发生的事件提供声音和语音反馈。请使用 IAccessibilitySignalService 在发生重要事件时播放信号(例如,出现错误、任务完成或内容发生变化)。

使用时机
  • 当事件已有对应信号时,使用现有信号(参见 AccessibilitySignal.* 静态成员,例如 AccessibilitySignal.error、AccessibilitySignal.terminalQuickFix、AccessibilitySignal.clear)。
  • 如果没有合适的现有信号,请联系 @meganrogge,讨论添加新信号。未经事先沟通,请勿注册新信号。
信号的工作方式

每个信号都有两种由用户设置控制的模式:

  • 声音 — 短促的音频提示,可设置为 auto(连接屏幕阅读器时开启)、on 或 off。
  • 播报 — 通过 aria-live 播放语音消息,可设置为 auto 或 off。
用法
// Inject the service via constructor parameter
constructor(
	@IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService
) { }

// Play a signal
this._accessibilitySignalService.playSignal(AccessibilitySignal.terminalQuickFix);

// Play with options
this._accessibilitySignalService.playSignal(AccessibilitySignal.error, { userGesture: true });

5. ARIA 警报与状态消息

请使用 src/vs/base/browser/ui/aria/aria.ts 中的 alert() 和 status() 函数,向屏幕阅读器播报动态变化。

alert(msg) — 强提示实时区域(role="alert")
  • 适用于:用户必须立即知晓的紧急重要信息。
  • 示例:错误、警告、关键状态变化、用户主动操作的结果。
  • 行为:会打断屏幕阅读器当前的播报。
status(msg) — 礼貌提示实时区域(aria-live="polite")
  • 适用于:非紧急的信息更新,应在屏幕阅读器空闲时播报。
  • 示例:进度更新、搜索结果数量、后台状态变化。
  • 行为:消息会排队,并在屏幕阅读器完成当前播报后播放。
指南
  • 优先使用 status() 而非 alert(),除非信息具有时效性,或是直接用户操作的结果。过度使用 alert() 会造成嘈杂且令人分心的体验。
  • 保持消息简洁。 屏幕阅读器会读出完整消息;消息过长会让用户等待。
  • 不要重复播报——如果无障碍信号已经播报了事件,就不要再为同一信息调用 alert() / status()。
  • 使用 nls.localize() 本地化所有消息。

6. 键盘导航

所有交互式 UI 元素都必须能够通过键盘完整操作。

要求
  • Tab 顺序:所有交互式元素都必须能通过 Tab / Shift+Tab 按逻辑顺序访问。
  • 方向键导航:列表、树、网格和工具栏必须按照 WAI-ARIA 模式支持方向键导航。
  • 焦点可见性:获得焦点的元素必须显示可见的焦点指示器(VS Code 的主题系统通过 focusBorder 提供此功能)。
  • 不得仅支持鼠标操作:所有可通过点击或悬停执行的操作,也必须能够通过键盘执行(上下文菜单、按钮、切换开关等)。
  • 按 Escape 关闭:覆盖层、对话框和弹出窗口必须能够通过 Escape 关闭,并将焦点返回到先前的元素。
  • 焦点陷阱:模态对话框必须将焦点限制在对话框内,直到对话框关闭。
说明不可用的选项

遵循可发现的不可用功能模式:文档中提及的功能,不应仅仅因为用户无法启用,就变得莫名其妙地不可见。使用控件支持的 ARIA 语义,同时播报禁用状态及其原因;仅使用灰色标签、公文包图标或悬停提示并不足够。文本已能表达含义时,图标应保持为装饰元素。

说明性链接必须能够独立通过鼠标和键盘操作,且不应触发已禁用的选项。请验证实际的 Tab/Shift+Tab 和 Enter 行为,而不只是检查 tabIndex 或以编程方式调用 focus();列表导航可能会跳过禁用的行。策略刷新时应保留焦点和选中状态,防止执行过时操作,并更新该功能的无障碍帮助,提供可用的说明和后续步骤。请复用设计指南中链接的设置、模型、测试框架和权限选择器示例。


7. ARIA 标签和角色

所有交互式 UI 元素都必须具有适当的 ARIA 属性,以便屏幕阅读器识别并描述它们。

要求
  • aria-label:每个没有可见文本的交互式元素(图标按钮、仅图标的操作、自定义控件)都必须有描述性的 aria-label。标签应进行本地化。
  • aria-labelledby / aria-describedby:使用这些属性将元素与现有的可见文本关联起来,而不是重复字符串。
  • role:未使用原生 HTML 元素的自定义控件必须声明正确的 ARIA 角色(例如,role="button"、role="tree"、role="tablist")。
  • aria-expanded、aria-selected、aria-checked:必须使用相应的 ARIA 状态属性传达切换和选择状态。
  • aria-hidden="true":装饰性或冗余元素(文本标签旁的图标、装饰性分隔符)必须从无障碍树中隐藏。
指南
  • 避免使用“按钮”或“图标”等笼统标签,应描述操作,例如:“关闭面板”、“切换侧边栏”、“运行任务”。
  • 使用屏幕阅读器进行测试(macOS 上的 VoiceOver、Windows 上的 NVDA),确认标签在上下文中能够正确读出。
  • 列表和树在虚拟化时应使用 aria-setsize 和 aria-posinset,以便屏幕阅读器报告正确的数量。

每项新功能的检查清单

  • 在 accessibleView.ts 中添加新的 AccessibleViewProviderId 条目
  • 在 accessibilityConfiguration.ts 中添加新的 AccessibilityVerbositySettingId 条目
  • 在配置属性中使用 ...baseVerbosityProperty 注册详细程度设置
  • 创建并注册 type = Help 的 IAccessibleViewImplementation
  • 内容提供程序引用正确的 verbositySettingKey
  • 使用 nls.localize() 完整本地化帮助文本
  • 帮助文本中的键绑定使用 <keybinding:commandId> 语法,以便动态解析
  • 设置 when 上下文键,确保只有在功能获得焦点时才显示对话框
  • 如果功能包含丰富/可视内容:创建并注册 type = View 的 IAccessibleViewImplementation
  • 在功能的 *.contribution.ts 文件中调用注册方法
  • 为重要事件播放无障碍信号(使用现有的 AccessibilitySignal.* 或注册新信号)
  • 对动态变化适当地使用 aria.alert() 或 aria.status()(除非情况紧急,否则优先使用 status())
  • 所有交互元素均可通过键盘访问和操作
  • 所有没有可见文本的交互元素都具有本地化的 aria-label
  • 自定义小组件声明正确的 ARIA role 和状态属性
  • 使用 aria-hidden="true" 隐藏装饰性元素

关键文件

  • src/vs/platform/accessibility/browser/accessibleView.ts — AccessibleViewProviderId、AccessibleContentProvider、IAccessibleViewContentProvider
  • src/vs/platform/accessibility/browser/accessibleViewRegistry.ts — AccessibleViewRegistry、IAccessibleViewImplementation
  • src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts — AccessibilityVerbositySettingId、详细程度设置注册
  • src/vs/platform/accessibilitySignal/browser/accessibilitySignalService.ts — IAccessibilitySignalService、AccessibilitySignal
  • src/vs/base/browser/ui/aria/aria.ts — 用于 ARIA 实时区域播报的 alert()、status()

评论

支持 Markdown