mcpField
D
dotnet-accessibility

dotnet-accessibility

github.com/wshaddix设计与 UI8678 次安装35 周前更新
代码仓库

关于 dotnet-accessibility

面向 .NET 界面开发者的无障碍 Skill,覆盖 Blazor、MAUI、WinUI 等框架。内容结合通用实践、框架专属 API 和测试指导。

主要功能

  1. 01语义标记与屏幕阅读器元数据
  2. 02键盘导航与焦点管理
  3. 03WCAG 色彩对比度指导
  4. 04Blazor、MAUI、WinUI 专属 API
  5. 05无障碍测试工具与检查清单

适用场景

  1. 01检查 .NET 界面的键盘与焦点行为
  2. 02为框架控件添加屏幕阅读器支持
  3. 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:17:1
大号文本(>= 18pt 或 14pt 粗体)3:14.5:1
UI 组件和图形对象3:13: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.NameSemanticProperties.Description
AutomationProperties.HelpTextSemanticProperties.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]。


无障碍测试工具

各平台测试
平台主要工具辅助工具
WindowsAccessibility Insights for Windows讲述人(Win+Ctrl+Enter)、Inspect.exe(Windows SDK)
Web(Blazor)axe-core / axe DevToolsLighthouse(Chrome)、WAVE、NVDA、VoiceOver(macOS)
AndroidAccessibility ScannerTalkBack、Android Studio Layout Inspector
iOS / macOSAccessibility 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)
手动测试清单
  1. 仅使用键盘导航——不使用鼠标,使用 Tab 键遍历整个应用;确认可以访问所有功能
  2. 屏幕阅读器完整体验——启用讲述人/VoiceOver/TalkBack,并遍历完整工作流
  3. 高对比度——启用系统高对比度主题,并确认所有内容仍然清晰可见
  4. 缩放/比例调整——将文本大小调至 200%,并确认布局不会错乱或裁切内容
  5. 颜色对比度——确认所有文本和交互元素均符合 WCAG AA 对比度比例(文本为 4.5:1,大号文本和 UI 组件为 3:1)

WCAG 参考

此技能以 Web 内容无障碍指南(WCAG) 作为全球无障碍标准。WCAG 2.1 是当前基准;WCAG 2.2 则新增了针对移动设备和认知无障碍的标准。

四项原则(POUR):

  1. 可感知 —— 信息必须以所有用户都能感知的方式呈现
  2. 可操作 —— 所有用户都必须能够操作 UI 组件
  3. 可理解 —— 信息和 UI 操作必须易于理解
  4. 健壮 —— 内容必须足够健壮,能够与辅助技术配合使用

符合性级别: A(最低)、AA(大多数应用推荐的目标)、AAA(增强)。大多数法律要求和行业标准的目标都是 WCAG 2.1 AA 级。

注意: 此技能提供技术实现指南,不构成有关无障碍合规要求的法律建议。相关要求因司法管辖区和应用类型而异。


Agent 常见注意事项

  1. 不要在 MAUI Label 控件上设置 SemanticProperties.Description。 它会覆盖屏幕阅读器使用的 Text 属性,导致视觉内容与语音内容不一致。Label 已可通过其 Text 属性实现无障碍访问。
  2. 不要在 Android 上的 MAUI Entry/Editor 上设置 SemanticProperties.Description。 请改用 Placeholder 或 SemanticProperties.Hint——Description 会与这些控件上的 TalkBack 操作冲突。
  3. 不要在新的 MAUI 代码中使用 AutomationProperties.Name 或 AutomationProperties.HelpText。 请改用 SemanticProperties(MAUI 原生 API)。AutomationProperties.IsInAccessibleTree 和 ExcludedWithChildren 仍可用于控制是否纳入无障碍树。
  4. 不要在仅含图标的 Blazor 按钮上省略 aria-label。 没有可见文本内容的按钮对屏幕阅读器不可见,除非设置了 aria-label 或 aria-labelledby。
  5. 不要对常规状态更新使用 aria-live="assertive"。 Assertive 会立即打断屏幕阅读器。非关键更新请使用 aria-live="polite";assertive 仅用于错误和时间紧迫的警报。
  6. 不要认为 TUI 应用默认就具备无障碍性。 终端屏幕阅读器的支持情况会因模拟器和操作系统而有很大差异。在关键无障碍场景中,始终提供其他输出格式。
  7. 不要在未验证对比度的情况下硬编码颜色。 请使用工具(Accessibility Insights、Lighthouse)验证是否符合 WCAG AA。还必须测试系统高对比度主题。
  8. 不要忘记为常用的 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,免费)

参考资料

评论

支持 Markdown