关于 dotnet-accessibility
面向 .NET 界面开发者的无障碍 Skill,覆盖 Blazor、MAUI、WinUI 等框架。内容结合通用实践、框架专属 API 和测试指导。
主要功能
- 01语义标记与屏幕阅读器元数据
- 02键盘导航与焦点管理
- 03WCAG 色彩对比度指导
- 04Blazor、MAUI、WinUI 专属 API
- 05无障碍测试工具与检查清单
适用场景
- 01检查 .NET 界面的键盘与焦点行为
- 02为框架控件添加屏幕阅读器支持
- 03在测试中核验对比度与无障碍性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-accessibility安装后在 Claude Code 中输入/dotnet-accessibility确认已加载。
详细介绍
dotnet-accessibility
面向 .NET UI 框架的跨平台无障碍模式:语义标记、键盘导航、焦点管理、颜色对比度和屏幕阅读器集成。深入介绍 Blazor(HTML ARIA)、MAUI(SemanticProperties)和 WinUI(AutomationProperties / UI Automation)。并简要提供 WPF、Uno Platform 和 TUI 框架的指南及交叉引用。
**范围边界:**此技能负责跨领域的无障碍原则和各框架的无障碍 API。框架特定的开发模式(项目设置、MVVM、路由、部署)由相应的框架技能负责。
**范围之外:**框架项目设置——请参阅各框架技能。法律合规建议——此技能会引用 WCAG 标准,但不提供法律指导。UI 框架选择——请参阅 [skill:dotnet-ui-chooser]。
交叉引用:[skill:dotnet-blazor-patterns] 介绍 Blazor 托管和渲染模式,[skill:dotnet-blazor-components] 介绍 Blazor 组件生命周期,[skill:dotnet-maui-development] 介绍 MAUI 模式,[skill:dotnet-winui] 介绍 WinUI 3 模式,[skill:dotnet-wpf-modern] 介绍 .NET 8+ 上的 WPF,[skill:dotnet-uno-platform] 介绍 Uno Platform 模式,[skill:dotnet-terminal-gui] 介绍 Terminal.Gui,[skill:dotnet-spectre-console] 介绍 Spectre.Console,[skill:dotnet-ui-chooser] 介绍框架选择。
跨平台原则
这些原则适用于所有 .NET UI 框架。后续章节将介绍各框架特定的实现方式。
语义标记
为所有交互式和信息性元素提供有意义的名称和描述。屏幕阅读器依赖语义元数据(而非视觉外观)来传达 UI 结构。
- 每个交互控件都必须有可访问名称(文本标签、ARIA 标签或自动化属性)
- 图像和图标必须有描述其用途的文本替代内容
- 装饰性元素应从无障碍树中隐藏
- 以合乎逻辑的方式对相关控件分组,让屏幕阅读器能够结合上下文播报
键盘导航
所有功能都必须仅通过键盘即可操作。无法使用鼠标、指针或触摸的用户完全依赖键盘交互。
- 保持符合视觉阅读顺序的逻辑 Tab 顺序
- 为所有交互元素提供可见的焦点指示器
- 支持标准键盘操作模式:使用 Tab/Shift+Tab 导航,Enter/Space 激活,Escape 关闭,复合控件内使用方向键
- 避免键盘陷阱——用户必须能够从每个控件移开
焦点管理
通过编程管理焦点,可确保屏幕阅读器正确播报上下文变化。
- 将焦点移至新显示的内容(对话框、展开的面板、内联通知)
- 关闭叠加层时,将焦点返回触发元素
- 避免在后台更新期间意外窃取焦点
- 页面或对话框加载时,将初始焦点设在主要操作上
颜色对比度
确保文本和交互元素符合 WCAG 对比度比例。
| 元素类型 | 最低比例(WCAG AA) | 增强比例(WCAG AAA) |
|---|---|---|
| 普通文本(< 18pt) | 4.5:1 | 7:1 |
| 大号文本(>= 18pt 或 14pt 粗体) | 3:1 | 4.5:1 |
| UI 组件和图形对象 | 3:1 | 3:1 |
- 不要仅依赖颜色传达信息(可补充使用图标、图案或文本标签)
- 支持高对比度主题和系统颜色覆盖
- 使用色盲模拟工具进行测试
Blazor 无障碍(深入介绍)
Blazor 会渲染 HTML,因此适用标准的 Web 无障碍模式。使用原生 HTML 语义和 ARIA 属性来构建无障碍 Blazor 应用。
语义 HTML 和 ARIA
@* Use semantic HTML elements for structure *@
<nav aria-label="Main navigation">
<ul>
<li><a href="/products">Products</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<main>
<h1>Product Catalog</h1>
@* Image with alt text *@
<img src="hero.png" alt="Product showcase displaying three featured items" />
@* Decorative image hidden from accessibility tree *@
<img src="divider.svg" alt="" role="presentation" />
@* Button with accessible name from content *@
<button @onclick="AddToCart">Add to Cart</button>
@* Icon button requires aria-label *@
<button @onclick="ToggleFavorite" aria-label="Add to favorites">
<span class="icon-heart" aria-hidden="true"></span>
</button>
</main>
键盘事件处理
<div role="listbox"
tabindex="0"
aria-label="Product list"
aria-activedescendant="@_activeId"
@onkeydown="HandleKeyDown"
@onkeydown:preventDefault>
@foreach (var product in Products)
{
<div id="@($"product-{product.Id}")"
role="option"
aria-selected="@(product.Id == SelectedId)"
@onclick="() => Select(product)">
@product.Name
</div>
}
</div>
@code {
private string _activeId = "";
private void HandleKeyDown(KeyboardEventArgs e)
{
switch (e.Key)
{
case "ArrowDown":
MoveSelection(1);
break;
case "ArrowUp":
MoveSelection(-1);
break;
case "Enter":
case " ":
ConfirmSelection();
break;
}
}
}
实时区域
在不移动焦点的情况下,向屏幕阅读器播报动态内容变化:
@* Polite: announced after current speech finishes *@
<div aria-live="polite" aria-atomic="true">
@if (_statusMessage is not null)
{
<p>@_statusMessage</p>
}
</div>
@* Assertive: interrupts current speech (use sparingly) *@
<div aria-live="assertive" role="alert">
@if (_errorMessage is not null)
{
<p>@_errorMessage</p>
}
</div>
表单无障碍
<EditForm Model="@_model" OnValidSubmit="HandleSubmit">
<DataAnnotationsValidator />
<div>
<label for="product-name">Product name</label>
<InputText id="product-name"
@bind-Value="_model.Name"
aria-describedby="name-error"
aria-invalid="@(_nameInvalid ? "true" : null)" />
<ValidationMessage For="() => _model.Name" id="name-error" />
</div>
<div>
<label for="quantity">Quantity</label>
<InputNumber id="quantity"
@bind-Value="_model.Quantity"
aria-describedby="quantity-help"
min="1" max="100" />
<span id="quantity-help">Enter a value between 1 and 100</span>
</div>
<button type="submit">Submit Order</button>
</EditForm>
有关 Blazor 托管模型和渲染模式配置,请参阅 [skill:dotnet-blazor-patterns]。有关组件生命周期和 EditForm 模式,请参阅 [skill:dotnet-blazor-components]。
MAUI 无障碍(深入介绍)
MAUI 提供 SemanticProperties 附加属性,作为推荐的无障碍 API。这些属性映射到原生平台的无障碍 API(iOS/macOS 上的 VoiceOver、Android 上的 TalkBack、Windows 上的 Narrator)。
SemanticProperties
<Image Source="product.png"
SemanticProperties.Description="Product photo showing a blue widget" />
<Button Text="Add to Cart"
SemanticProperties.Hint="Adds the current product to your shopping cart" />
<Label Text="Order Summary"
SemanticProperties.HeadingLevel="Level1" />
<Label Text="Items"
SemanticProperties.HeadingLevel="Level2" />
主要 API:
SemanticProperties.Description-- 屏幕阅读器播报的简短文本(相当于 iOS 上的accessibilityLabel、Android 上的contentDescription)SemanticProperties.Hint-- 补充用途说明(相当于 iOS 上的accessibilityHint)SemanticProperties.HeadingLevel-- 标记标题(Level1 到 Level9);Android 和 iOS 仅支持一个标题级别,Windows 支持全部 9 个级别
平台警告: 不要在 Label 上设置 SemanticProperties.Description,这会覆盖屏幕阅读器读取的 Text 属性,导致视觉文本与语音播报不一致。在 Android 上,不要为 Entry 或 Editor 设置 SemanticProperties.Description,请改用 Placeholder 或 SemanticProperties.Hint,因为 Description 会与 TalkBack 操作冲突。
旧版 AutomationProperties
AutomationProperties 是较早的 Xamarin.Forms API,在 MAUI 中已由 SemanticProperties 取代。新代码请使用 SemanticProperties。
| 旧版 API | 替代项 |
|---|---|
AutomationProperties.Name | SemanticProperties.Description |
AutomationProperties.HelpText | SemanticProperties.Hint |
AutomationProperties.LabeledBy | 将 SemanticProperties.Description 绑定到标签的 Text |
AutomationProperties.IsInAccessibleTree 和 AutomationProperties.ExcludedWithChildren 仍可用于控制是否将元素纳入无障碍树。
以编程方式设置焦点和播报
// Move screen reader focus to a specific element
myLabel.SetSemanticFocus();
// Announce text to the screen reader without moving focus
SemanticScreenReader.Default.Announce("Item added to cart successfully.");
无障碍自定义控件
构建自定义控件时,请确保设置无障碍元数据:
public class RatingControl : ContentView
{
private int _rating;
public int Rating
{
get => _rating;
set
{
_rating = value;
SemanticProperties.SetDescription(this,
$"Rating: {value} out of 5 stars");
SemanticScreenReader.Default.Announce(
$"Rating changed to {value} stars");
}
}
}
有关 MAUI 项目结构、MVVM 模式和平台服务,请参阅 [skill:dotnet-maui-development]。
WinUI 无障碍(深入介绍)
WinUI 3 / Windows App SDK 基于 Microsoft UI Automation 框架构建。内置控件默认支持自动化。自定义控件则需要自动化 Peer。
AutomationProperties
<Image Source="ms-appx:///Assets/product.png"
AutomationProperties.Name="Product photo showing a blue widget" />
<Button Content="Add to Cart"
AutomationProperties.HelpText="Adds the current product to your shopping cart" />
<TextBlock x:Name="QuantityLabel" Text="Quantity:" />
<NumberBox AutomationProperties.LabeledBy="{x:Bind QuantityLabel}"
Value="{x:Bind ViewModel.Quantity, Mode=TwoWay}" />
<Image Source="ms-appx:///Assets/divider.png"
AutomationProperties.AccessibilityView="Raw" />
自定义自动化 Peer
对于自定义控件,请实现 AutomationPeer,以便向 UI Automation 客户端公开控件:
// Custom control
public sealed class StarRating : Control
{
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(nameof(Value), typeof(int),
typeof(StarRating), new PropertyMetadata(0, OnValueChanged));
private static void OnValueChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
if (FrameworkElementAutomationPeer
.FromElement((StarRating)d) is StarRatingAutomationPeer peer)
{
peer.RaiseValueChanged((int)e.OldValue, (int)e.NewValue);
}
}
protected override AutomationPeer OnCreateAutomationPeer()
=> new StarRatingAutomationPeer(this);
}
// Automation peer (using Microsoft.UI.Xaml.Automation.Provider)
public sealed class StarRatingAutomationPeer
: FrameworkElementAutomationPeer, IRangeValueProvider
{
private StarRating Owner => (StarRating)base.Owner;
public StarRatingAutomationPeer(StarRating owner) : base(owner) { }
protected override string GetClassNameCore() => nameof(StarRating);
protected override string GetNameCore()
=> $"Rating: {Owner.Value} out of 5 stars";
protected override AutomationControlType GetAutomationControlTypeCore()
=> AutomationControlType.Slider;
// IRangeValueProvider
public double Value => Owner.Value;
public double Minimum => 0;
public double Maximum => 5;
public double SmallChange => 1;
public double LargeChange => 1;
public bool IsReadOnly => false;
public void SetValue(double value)
=> Owner.Value = (int)Math.Clamp(value, Minimum, Maximum);
public void RaiseValueChanged(int oldValue, int newValue)
{
RaisePropertyChangedEvent(
RangeValuePatternIdentifiers.ValueProperty,
(double)oldValue, (double)newValue);
}
}
WinUI 中的键盘无障碍
WinUI XAML 控件提供内置键盘支持。请确保自定义控件遵循相同模式:
<TextBox Header="First name" TabIndex="1" />
<TextBox Header="Last name" TabIndex="2" />
<Button Content="Submit" TabIndex="3" />
<Button Content="Save" AccessKey="S" />
<Button Content="Delete" AccessKey="D" />
有关 WinUI 项目设置、XAML 模式和 Windows 集成,请参阅 [skill:dotnet-winui]。
WPF 无障碍(简要介绍)
.NET 8+ 上的 WPF 使用与 WinUI 相同的 UI Automation 框架。两者的 API 几乎相同,仅命名空间有所不同。
AutomationProperties.Name、AutomationProperties.HelpText、AutomationProperties.LabeledBy的工作方式与 WinUI 中相同- 自定义控件重写
OnCreateAutomationPeer(),并返回FrameworkElementAutomationPeer的子类 - WPF Fluent 主题(.NET 9+)自动包含高对比度支持
- 使用
AutomationProperties.LiveSetting播报实时区域更新
<Image Source="product.png"
AutomationProperties.Name="Product photo" />
<TextBlock x:Name="StatusLabel"
AutomationProperties.LiveSetting="Polite"
Text="{Binding StatusText}" />
有关 .NET 8+ 上的 WPF 开发模式,请参阅 [skill:dotnet-wpf-modern]。
Uno Platform 无障碍功能(简介)
Uno Platform 遵循 UWP/WinUI 的 AutomationProperties 模式,因为其 API 表面与 WinUI 兼容。
AutomationProperties.Name、AutomationProperties.HelpText、AutomationProperties.LabeledBy可跨平台使用- 自定义
AutomationPeer实现遵循 WinUI 模式 - 在 WebAssembly 上,Uno 会自动将
AutomationProperties映射到 HTML ARIA 属性 - 各平台的具体行为可能有所不同——请在每个目标平台(Windows、iOS、Android、WASM)上进行测试
有关 Uno Platform 开发模式,请参阅 [skill:dotnet-uno-platform]。有关各目标平台的部署和测试,请参阅 [skill:dotnet-uno-targets]。
TUI 无障碍功能(简介)
终端 UI 框架存在固有的无障碍功能限制。屏幕阅读器支持取决于终端模拟器和操作系统。
Terminal.Gui(v2):
- 屏幕阅读器可通过终端模拟器的无障碍功能读取终端文本内容
- 没有等同于 ARIA 或 AutomationProperties 的编程式无障碍 API
- 逻辑焦点顺序遵循视图上的
TabIndex属性 - 高对比度由终端颜色主题管理,而非应用管理
Spectre.Console:
- 仅输出库——屏幕阅读器直接读取终端文本缓冲区
- 无障碍功能至关重要时,为复杂的视觉元素(表格、树)使用纯文本回退方案
AnsiConsole.Profile.Capabilities可检测终端功能,但无法检测屏幕阅读器是否存在
客观限制: TUI 应用无法通过编程方式控制屏幕阅读器的行为。终端模拟器提供的无障碍功能支持各不相同。对于将无障碍功能视为硬性要求的应用,请考虑使用 GUI 框架(Blazor、MAUI、WinUI)。
有关 Terminal.Gui 模式,请参阅 [skill:dotnet-terminal-gui]。有关 Spectre.Console 模式,请参阅 [skill:dotnet-spectre-console]。
无障碍测试工具
各平台测试
| 平台 | 主要工具 | 辅助工具 |
|---|---|---|
| Windows | Accessibility Insights for Windows | 讲述人(Win+Ctrl+Enter)、Inspect.exe(Windows SDK) |
| Web(Blazor) | axe-core / axe DevTools | Lighthouse(Chrome)、WAVE、NVDA、VoiceOver(macOS) |
| Android | Accessibility Scanner | TalkBack、Android Studio Layout Inspector |
| iOS / macOS | Accessibility Inspector(Xcode) | VoiceOver(内置)、XCUITest 无障碍断言 |
自动化测试集成
// Blazor: integrate axe-core with Playwright for automated accessibility testing
// Requires: Deque.AxeCore.Playwright NuGet package
// Install: dotnet add package Deque.AxeCore.Playwright
var axeResults = await new Deque.AxeCore.Playwright.AxeBuilder(page)
.AnalyzeAsync();
// Check for violations
Assert.Empty(axeResults.Violations);
// WinUI/WPF: use Accessibility Insights for Windows CLI in CI pipelines
// Requires: AccessibilityInsights.CLI (available via Microsoft Store or direct download)
手动测试清单
- 仅使用键盘导航——不使用鼠标,使用 Tab 键遍历整个应用;确认可以访问所有功能
- 屏幕阅读器完整体验——启用讲述人/VoiceOver/TalkBack,并遍历完整工作流
- 高对比度——启用系统高对比度主题,并确认所有内容仍然清晰可见
- 缩放/比例调整——将文本大小调至 200%,并确认布局不会错乱或裁切内容
- 颜色对比度——确认所有文本和交互元素均符合 WCAG AA 对比度比例(文本为 4.5:1,大号文本和 UI 组件为 3:1)
WCAG 参考
此技能以 Web 内容无障碍指南(WCAG) 作为全球无障碍标准。WCAG 2.1 是当前基准;WCAG 2.2 则新增了针对移动设备和认知无障碍的标准。
四项原则(POUR):
- 可感知 —— 信息必须以所有用户都能感知的方式呈现
- 可操作 —— 所有用户都必须能够操作 UI 组件
- 可理解 —— 信息和 UI 操作必须易于理解
- 健壮 —— 内容必须足够健壮,能够与辅助技术配合使用
符合性级别: A(最低)、AA(大多数应用推荐的目标)、AAA(增强)。大多数法律要求和行业标准的目标都是 WCAG 2.1 AA 级。
注意: 此技能提供技术实现指南,不构成有关无障碍合规要求的法律建议。相关要求因司法管辖区和应用类型而异。
Agent 常见注意事项
- 不要在 MAUI
Label控件上设置SemanticProperties.Description。 它会覆盖屏幕阅读器使用的Text属性,导致视觉内容与语音内容不一致。Label已可通过其Text属性实现无障碍访问。 - 不要在 Android 上的 MAUI
Entry/Editor上设置SemanticProperties.Description。 请改用Placeholder或SemanticProperties.Hint——Description会与这些控件上的 TalkBack 操作冲突。 - 不要在新的 MAUI 代码中使用
AutomationProperties.Name或AutomationProperties.HelpText。 请改用SemanticProperties(MAUI 原生 API)。AutomationProperties.IsInAccessibleTree和ExcludedWithChildren仍可用于控制是否纳入无障碍树。 - 不要在仅含图标的 Blazor 按钮上省略
aria-label。 没有可见文本内容的按钮对屏幕阅读器不可见,除非设置了aria-label或aria-labelledby。 - 不要对常规状态更新使用
aria-live="assertive"。 Assertive 会立即打断屏幕阅读器。非关键更新请使用aria-live="polite";assertive 仅用于错误和时间紧迫的警报。 - 不要认为 TUI 应用默认就具备无障碍性。 终端屏幕阅读器的支持情况会因模拟器和操作系统而有很大差异。在关键无障碍场景中,始终提供其他输出格式。
- 不要在未验证对比度的情况下硬编码颜色。 请使用工具(Accessibility Insights、Lighthouse)验证是否符合 WCAG AA。还必须测试系统高对比度主题。
- 不要忘记为常用的 WinUI/WPF 按钮设置
AccessKey。 访问键(Alt+按键快捷键)对依赖键盘的用户至关重要,而且添加起来非常简单。
前置条件
- .NET 8.0+(所有框架的基准版本)
- 特定框架的 SDK:MAUI 工作负载、Windows App SDK(WinUI)、Blazor 项目模板
- 测试工具:Accessibility Insights(Windows)、axe-core(Web)、Xcode Accessibility Inspector(macOS/iOS)
- 手动测试用屏幕阅读器:Narrator(Windows)、VoiceOver(macOS/iOS)、TalkBack(Android)、NVDA(Windows,免费)