.NET可访问性 dotnet-accessibility

.NET UI 框架的可访问性实现技能,涵盖语义标记、键盘导航、焦点管理、颜色对比和屏幕阅读器集成。针对 Blazor、MAUI、WinUI 等框架提供跨平台无障碍开发指导,关键词:.NET、可访问性、无障碍、UI 开发、Blazor、MAUI、WinUI、WCAG。

前端开发 0 次安装 0 次浏览 更新于 3/6/2026

名称: dotnet-accessibility 描述: “实现可访问的 .NET UI。语义属性、ARIA、自动化对等体、每个平台的测试。” 用户可调用: false

dotnet-accessibility

跨平台的 .NET UI 框架可访问性模式:语义标记、键盘导航、焦点管理、颜色对比和屏幕阅读器集成。深入覆盖 Blazor(HTML ARIA)、MAUI(语义属性)和 WinUI(自动化属性/UI 自动化)。为 WPF、Uno 平台和 TUI 框架提供简要指导与交叉参考。

范围

  • 跨平台可访问性原则(语义标记、键盘导航、焦点、对比度)
  • Blazor 可访问性(HTML ARIA 属性、屏幕阅读器模式)
  • MAUI 可访问性(语义属性、平台特定设置)
  • WinUI 可访问性(自动化属性、UI 自动化、自动化对等体)
  • WPF、Uno 平台和 TUI 可访问性指导与交叉参考

超出范围

  • 框架项目设置 – 参见各个框架技能
  • 法律合规建议(引用 WCAG 但不提供法律指导)
  • UI 框架选择 – 参见 [技能:dotnet-ui-选择器]

交叉参考:[技能:dotnet-blazor-模式] 用于 Blazor 托管和渲染模式,[技能:dotnet-blazor-组件] 用于 Blazor 组件生命周期,[技能:dotnet-maui-开发] 用于 MAUI 模式,[技能:dotnet-winui] 用于 WinUI 3 模式,[技能:dotnet-wpf-现代] 用于 .NET 8+ 上的 WPF,[技能:dotnet-uno-平台] 用于 Uno 平台模式,[技能:dotnet-终端-gui] 用于 Terminal.Gui,[技能:dotnet-spectre-控制台] 用于 Spectre.Console,[技能:dotnet-ui-选择器] 用于框架选择。


跨平台原则

这些原则适用于所有 .NET UI 框架。框架特定实现在后续部分中。

语义标记

为所有交互式和信息元素提供有意义的名称和描述。屏幕阅读器依赖于语义元数据 – 而不是视觉外观 – 来传达 UI 结构。

  • 每个交互式控件必须有一个可访问名称(文本标签、ARIA 标签或自动化属性)
  • 图像和图标必须有描述其目的的文本替代
  • 装饰性元素应从可访问性树中隐藏
  • 逻辑分组相关控件,以便屏幕阅读器在上下文中宣布它们

键盘导航

所有功能必须仅通过键盘即可操作。不能使用鼠标、指针或触摸的用户完全依赖于键盘交互。

  • 维持一个遵循视觉阅读流程的逻辑选项卡顺序
  • 在所有交互式元素上提供可见焦点指示器
  • 支持标准键盘模式: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

@* 使用语义 HTML 元素进行结构 *@
<nav aria-label="主导航">
    <ul>
        <li><a href="/products">产品</a></li>
        <li><a href="/about">关于</a></li>
    </ul>
</nav>

<main>
    <h1>产品目录</h1>

    @* 带有 alt 文本的图像 *@
    <img src="hero.png" alt="产品展示显示三个特色项目" />

    @* 从可访问性树隐藏的装饰性图像 *@
    <img src="divider.svg" alt="" role="presentation" />

    @* 按钮,可访问名称来自内容 *@
    <button @onclick="添加到购物车">添加到购物车</button>

    @* 图标按钮需要 aria-label *@
    <button @onclick="切换收藏" aria-label="添加到收藏">
        <span class="icon-heart" aria-hidden="true"></span>
    </button>
</main>

键盘事件处理

<div role="listbox"
     tabindex="0"
     aria-label="产品列表"
     aria-activedescendant="@_activeId"
     @onkeydown="处理按键"
     @onkeydown:preventDefault>
    @foreach (var product in 产品列表)
    {
        <div id="@($"product-{product.Id}")"
             role="option"
             aria-selected="@(product.Id == 选中Id)"
             @onclick="() => 选择(product)">
            @product.名称
        </div>
    }
</div>

@code {
    private string _activeId = "";

    private void 处理按键(KeyboardEventArgs e)
    {
        switch (e.Key)
        {
            case "ArrowDown":
                移动选择(1);
                break;
            case "ArrowUp":
                移动选择(-1);
                break;
            case "Enter":
            case " ":
                确认选择();
                break;
        }
    }
}

实时区域

在不移动焦点的情况下,向屏幕阅读器宣布动态内容更改:

@* 礼貌:在当前讲话结束后宣布 *@
<div aria-live="polite" aria-atomic="true">
    @if (_statusMessage is not null)
    {
        <p>@_statusMessage</p>
    }
</div>

@* 断言:中断当前讲话(谨慎使用) *@
<div aria-live="assertive" role="alert">
    @if (_errorMessage is not null)
    {
        <p>@_errorMessage</p>
    }
</div>

表单可访问性

<EditForm Model="@_model" OnValidSubmit="处理提交">
    <DataAnnotationsValidator />

    <div>
        <label for="product-name">产品名称</label>
        <InputText id="product-name"
                   @bind-Value="_model.名称"
                   aria-describedby="name-error"
                   aria-invalid="@(_nameInvalid ? "true" : null)" />
        <ValidationMessage For="() => _model.名称" id="name-error" />
    </div>

    <div>
        <label for="quantity">数量</label>
        <InputNumber id="quantity"
                     @bind-Value="_model.数量"
                     aria-describedby="quantity-help"
                     min="1" max="100" />
        <span id="quantity-help">输入 1 到 100 之间的值</span>
    </div>

    <button type="submit">提交订单</button>
</EditForm>

对于 Blazor 托管模型和渲染模式配置,参见 [技能:dotnet-blazor-模式]。对于组件生命周期和 EditForm 模式,参见 [技能:dotnet-blazor-组件]。


MAUI 可访问性(深度)

MAUI 提供 SemanticProperties 附加属性作为推荐的可访问性 API。这些映射到原生平台可访问性 API(iOS/macOS 上的 VoiceOver,Android 上的 TalkBack,Windows 上的 Narrator)。

SemanticProperties

<!-- 描述:主要屏幕阅读器宣布 -->
<Image Source="product.png"
       SemanticProperties.Description="产品照片显示蓝色小部件" />

<!-- 提示:关于操作的额外上下文 -->
<Button Text="添加到购物车"
        SemanticProperties.Hint="将当前产品添加到您的购物车" />

<!-- HeadingLevel:启用基于标题的导航 -->
<Label Text="订单摘要"
       SemanticProperties.HeadingLevel="Level1" />
<Label Text="项目"
       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 上的 EntryEditor 上设置 SemanticProperties.Description – 使用 PlaceholderSemanticProperties.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.IsInAccessibleTreeAutomationProperties.ExcludedWithChildren 对于控制可访问性树包含仍然有用。

程序化焦点和宣布

// 将屏幕阅读器焦点移动到特定元素
myLabel.SetSemanticFocus();

// 在不移动焦点的情况下,向屏幕阅读器宣布文本
SemanticScreenReader.Default.Announce("成功将项目添加到购物车。");

可访问的自定义控件

构建自定义控件时,确保设置可访问性元数据:

public class RatingControl : ContentView
{
    private int _rating;

    public int Rating
    {
        get => _rating;
        set
        {
            _rating = value;
            SemanticProperties.SetDescription(this,
                $"评分:{value} 颗星,共 5 颗");
            SemanticScreenReader.Default.Announce(
                $"评分更改为 {value} 颗星");
        }
    }
}

对于 MAUI 项目结构、MVVM 模式和平台服务,参见 [技能:dotnet-maui-开发]。


WinUI 可访问性(深度)

WinUI 3 / Windows App SDK 基于 Microsoft UI 自动化框架。内置控件默认包括自动化支持。自定义控件需要自动化对等体。

AutomationProperties

<!-- 名称:屏幕阅读器的主要可访问名称 -->
<Image Source="ms-appx:///Assets/product.png"
       AutomationProperties.Name="产品照片显示蓝色小部件" />

<!-- HelpText:补充描述 -->
<Button Content="添加到购物车"
        AutomationProperties.HelpText="将当前产品添加到您的购物车" />

<!-- LabeledBy:将标签与控件关联 -->
<TextBlock x:Name="QuantityLabel" Text="数量:" />
<NumberBox AutomationProperties.LabeledBy="{x:Bind QuantityLabel}"
           Value="{x:Bind ViewModel.Quantity, Mode=TwoWay}" />

<!-- 从可访问性树隐藏装饰性元素 -->
<Image Source="ms-appx:///Assets/divider.png"
       AutomationProperties.AccessibilityView="Raw" />

自定义自动化对等体

对于自定义控件,实现 AutomationPeer 以将控件暴露给 UI 自动化客户端:

// 自定义控件
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);
}

// 自动化对等体(使用 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()
        => $"评分:{Owner.Value} 颗星,共 5 颗";
    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 控件提供内置键盘支持。确保自定义控件遵循相同模式:

<!-- TabIndex 控制导航顺序 -->
<TextBox Header="名" TabIndex="1" />
<TextBox Header="姓" TabIndex="2" />
<Button Content="提交" TabIndex="3" />

<!-- AccessKey 提供键盘快捷键(Alt + 键) -->
<Button Content="保存" AccessKey="S" />
<Button Content="删除" AccessKey="D" />

对于 WinUI 项目设置、XAML 模式和 Windows 集成,参见 [技能:dotnet-winui]。


WPF 可访问性(简要)

.NET 8+ 上的 WPF 使用与 WinUI 相同的 UI 自动化框架。API 几乎相同,但命名空间不同。

  • AutomationProperties.NameAutomationProperties.HelpTextAutomationProperties.LabeledBy 与 WinUI 中的相同
  • 自定义控件覆盖 OnCreateAutomationPeer() 并返回 FrameworkElementAutomationPeer 子类
  • WPF Fluent 主题(.NET 9+)自动包括高对比度支持
  • 使用 AutomationProperties.LiveSetting 进行实时区域宣布
<!-- WPF 可访问性遵循与 WinUI 相同的模式 -->
<Image Source="product.png"
       AutomationProperties.Name="产品照片" />

<TextBlock x:Name="StatusLabel"
           AutomationProperties.LiveSetting="Polite"
           Text="{Binding StatusText}" />

对于 .NET 8+ 上的 WPF 开发模式,参见 [技能:dotnet-wpf-现代]。


Uno 平台可访问性(简要)

Uno 平台遵循 UWP/WinUI AutomationProperties 模式,因为其 API 表面是 WinUI 兼容的。

  • AutomationProperties.NameAutomationProperties.HelpTextAutomationProperties.LabeledBy 跨平台工作
  • 自定义 AutomationPeer 实现遵循 WinUI 模式
  • 在 WebAssembly 上,Uno 自动将 AutomationProperties 映射到 HTML ARIA 属性
  • 平台特定行为可能不同 – 在每个目标(Windows、iOS、Android、WASM)上进行测试

对于 Uno 平台开发模式,参见 [技能:dotnet-uno-平台]。对于每个目标的部署和测试,参见 [技能:dotnet-uno-目标]。


TUI 可访问性(简要)

终端 UI 框架具有固有的可访问性限制。屏幕阅读器支持取决于终端仿真器和操作系统。

Terminal.Gui(v2):

  • 屏幕阅读器可以通过终端仿真器的可访问性支持读取终端文本内容
  • 没有等同于 ARIA 或 AutomationProperties 的程序化可访问性 API
  • 逻辑选项卡顺序遵循视图上的 TabIndex 属性
  • 高对比度由终端颜色主题管理,而不是应用程序

Spectre.Console:

  • 只输出库 – 屏幕阅读器直接读取终端文本缓冲区
  • 当可访问性关键时,使用纯文本回退用于复杂视觉元素(表格、树)
  • AnsiConsole.Profile.Capabilities 可以检测终端功能但不能检测屏幕阅读器存在

诚实约束: TUI 应用程序不能程序化控制屏幕阅读器行为。终端仿真器提供不同程度的可访问性支持。对于可访问性是硬性要求的应用程序,考虑使用 GUI 框架(Blazor、MAUI、WinUI)替代。

对于 Terminal.Gui 模式,参见 [技能:dotnet-终端-gui]。对于 Spectre.Console 模式,参见 [技能:dotnet-spectre-控制台]。


可访问性测试工具

每个平台的测试

平台 主要工具 次要工具
Windows Accessibility Insights for Windows Narrator(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:将 axe-core 与 Playwright 集成用于自动化可访问性测试
// 需要:Deque.AxeCore.Playwright NuGet 包
// 安装:dotnet add package Deque.AxeCore.Playwright
var axeResults = await new Deque.AxeCore.Playwright.AxeBuilder(page)
    .AnalyzeAsync();

// 检查违规
Assert.Empty(axeResults.Violations);

// WinUI/WPF:在 CI 管道中使用 Accessibility Insights for Windows CLI
// 需要:AccessibilityInsights.CLI(可通过 Microsoft Store 或直接下载获得)

手动测试清单

  1. 仅键盘导航 – 通过键盘无鼠标选项卡遍历整个应用;验证所有功能可达
  2. 屏幕阅读器演练 – 启用 Narrator/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。

注意: 此技能提供技术实施指导。不构成关于可访问性合规要求的法律建议,这些要求因管辖区和应用类型而异。


代理注意事项

  1. 不要在 MAUI Label 控件上设置 SemanticProperties.Description 它会覆盖屏幕阅读器的 Text 属性,导致视觉和口头内容不匹配。标签已经通过其 Text 属性可访问。
  2. 不要在 Android 上的 MAUI Entry/Editor 上设置 SemanticProperties.Description 使用 PlaceholderSemanticProperties.Hint 替代 – Description 与这些控件的 TalkBack 操作冲突。
  3. 对于新的 MAUI 代码,不要使用 AutomationProperties.NameAutomationProperties.HelpText 使用 SemanticProperties 替代(MAUI 原生 API)。AutomationProperties.IsInAccessibleTreeExcludedWithChildren 对于控制可访问性树包含仍然有效。
  4. 不要在仅图标的 Blazor 按钮上省略 aria-label 没有可见文本内容的按钮对屏幕阅读器不可见,除非设置 aria-labelaria-labelledby
  5. 不要将 aria-live="assertive" 用于常规状态更新。 断言会立即中断屏幕阅读器。使用 aria-live="polite" 用于非关键更新;保留断言用于错误和时间关键警报。
  6. 不要假设 TUI 应用默认可访问。 终端屏幕阅读器支持因仿真器和 OS 而异。对于关键可访问性场景,始终提供替代输出格式。
  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,免费)

参考