Skip to main content
Avalonia provides comprehensive accessibility support through the UI Automation framework, enabling applications to work with screen readers, keyboard navigation, and assistive technologies.

Accessibility Architecture

Avalonia’s accessibility system is based on:
  1. AutomationProperties - Attached properties for metadata
  2. AutomationPeers - Expose controls to assistive technologies
  3. Platform Integration - Maps to native accessibility APIs
    • Windows: UI Automation (UIA)
    • macOS: NSAccessibility
    • Linux: AT-SPI (Assistive Technology Service Provider Interface)

AutomationProperties

From ~/workspace/source/src/Avalonia.Controls/Automation/AutomationProperties.cs:

Basic Properties

<Button AutomationProperties.Name="Submit Form"
        AutomationProperties.HelpText="Submits the form data to the server"
        AutomationProperties.AutomationId="SubmitButton">
    Submit
</Button>

<TextBox AutomationProperties.Name="Email Address"
         AutomationProperties.HelpText="Enter your email address"
         AutomationProperties.IsRequiredForForm="True" />

<Image Source="logo.png"
       AutomationProperties.Name="Company Logo" />

Available Automation Properties

Identity and Description:
  • Name - Accessible name for the element
  • AutomationId - Unique identifier for automation
  • HelpText - Detailed description or help text
  • AccessKey - Keyboard access key (e.g., “Alt+F” for File menu)
  • AcceleratorKey - Keyboard shortcut (e.g., “Ctrl+S” for Save)
Control Type:
  • ControlTypeOverride - Override the control type
  • ClassNameOverride - Override the class name
  • AccessibilityView - Control visibility in automation tree
Relationships:
  • LabeledBy - Reference to labeling element
  • LiveSetting - Live region behavior (Off, Polite, Assertive)
Landmarks and Navigation:
  • LandmarkType - Semantic landmark (Banner, Main, Navigation, etc.)
  • HeadingLevel - Heading level (1-6)
  • PositionInSet - Position in a set of items
  • SizeOfSet - Total number of items in set
Behavior:
  • IsOffscreenBehavior - How to handle offscreen state
  • IsControlElementOverride - Override control element visibility

Property Examples

Labeling Form Fields

<StackPanel>
    <TextBlock Name="EmailLabel" 
               Text="Email Address:" />
    <TextBox AutomationProperties.LabeledBy="{Binding #EmailLabel}"
             AutomationProperties.HelpText="Enter your email address for notifications" />
</StackPanel>

Live Regions

<TextBlock AutomationProperties.LiveSetting="Assertive"
           Text="{Binding ErrorMessage}" />

<StackPanel AutomationProperties.LiveSetting="Polite">
    <TextBlock Text="{Binding StatusMessage}" />
</StackPanel>
LiveSetting values:
  • Off - No announcements (default)
  • Polite - Announce when user is idle
  • Assertive - Announce immediately, interrupting speech

Landmarks

<Border AutomationProperties.LandmarkType="Banner">
    <StackPanel>
        <Image Source="logo.png" 
               AutomationProperties.Name="Site Logo" />
        <Menu AutomationProperties.LandmarkType="Navigation">
            <!-- Navigation menu -->
        </Menu>
    </StackPanel>
</Border>

<Border AutomationProperties.LandmarkType="Main">
    <!-- Main content -->
</Border>

<Border AutomationProperties.LandmarkType="ContentInfo">
    <!-- Footer content -->
</Border>
Available landmark types:
  • Banner - Site header/banner
  • Main - Main content area
  • Navigation - Navigation menu
  • Search - Search functionality
  • Form - Form region
  • Complementary - Complementary content (sidebar)
  • ContentInfo - Footer/metadata
  • Region - Generic landmark region

Headings

<TextBlock Text="Main Title"
           FontSize="24"
           FontWeight="Bold"
           AutomationProperties.HeadingLevel="1" />

<TextBlock Text="Section Header"
           FontSize="18"
           FontWeight="Bold"
           AutomationProperties.HeadingLevel="2" />

Automation Peers

From ~/workspace/source/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs:

Understanding AutomationPeers

Automation peers expose controls to assistive technologies. Each control has an associated peer that provides:
  • Control type and classification
  • Current state and properties
  • Available patterns (interactions)
  • Hierarchical relationships

Control Types

public enum AutomationControlType
{
    None, Button, Calendar, CheckBox, ComboBox, ComboBoxItem,
    Edit, Hyperlink, Image, ListItem, List, Menu, MenuBar,
    MenuItem, ProgressBar, RadioButton, ScrollBar, Slider,
    Spinner, StatusBar, Tab, TabItem, Text, ToolBar, ToolTip,
    Tree, TreeItem, Custom, Group, Thumb, DataGrid, DataItem,
    Document, SplitButton, Window, Pane, Header, HeaderItem,
    Table, TitleBar, Separator, Expander
}

Creating Custom Automation Peers

using Avalonia.Automation.Peers;
using Avalonia.Controls;

public class CustomControl : Control
{
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new CustomControlAutomationPeer(this);
    }
}

public class CustomControlAutomationPeer : ControlAutomationPeer
{
    public CustomControlAutomationPeer(CustomControl owner) : base(owner)
    {
    }

    protected override AutomationControlType GetAutomationControlTypeCore()
    {
        return AutomationControlType.Custom;
    }

    protected override string GetClassNameCore()
    {
        return "CustomControl";
    }

    protected override string GetNameCore()
    {
        var owner = (CustomControl)Owner;
        return owner.AccessibleName ?? base.GetNameCore();
    }

    protected override bool IsControlElementCore()
    {
        return true;
    }

    protected override bool IsContentElementCore()
    {
        return true;
    }
}

Implementing Automation Patterns

using Avalonia.Automation.Provider;

public class ToggleButtonAutomationPeer : ButtonAutomationPeer,
    IToggleProvider
{
    public ToggleButtonAutomationPeer(ToggleButton owner) : base(owner)
    {
    }

    protected override object? GetPatternCore(AutomationPattern pattern)
    {
        if (pattern == AutomationPattern.Toggle)
            return this;
        return base.GetPatternCore(pattern);
    }

    // IToggleProvider implementation
    public ToggleState ToggleState
    {
        get
        {
            var owner = (ToggleButton)Owner;
            return owner.IsChecked == true ? ToggleState.On :
                   owner.IsChecked == false ? ToggleState.Off :
                   ToggleState.Indeterminate;
        }
    }

    public void Toggle()
    {
        if (!IsEnabled())
            throw new ElementNotEnabledException();
        
        var owner = (ToggleButton)Owner;
        owner.Toggle();
    }
}

Common Automation Patterns

From ~/workspace/source/src/Avalonia.Controls/Automation/Provider/:
  • IInvokeProvider - Invokable controls (buttons)
  • IToggleProvider - Toggle controls (checkboxes)
  • ISelectionProvider - Selection containers (list boxes)
  • ISelectionItemProvider - Selectable items
  • IValueProvider - Value controls (text boxes, sliders)
  • IRangeValueProvider - Range controls (sliders, progress bars)
  • IExpandCollapseProvider - Expandable controls (tree view items)
  • IScrollProvider - Scrollable containers

Keyboard Navigation

Tab Navigation

<StackPanel>
    <Button TabIndex="1" Content="First" />
    <Button TabIndex="3" Content="Third" />
    <Button TabIndex="2" Content="Second" />
    <Button IsTabStop="False" Content="Not in tab order" />
</StackPanel>
Properties:
  • TabIndex - Order in tab sequence (default: 0)
  • IsTabStop - Include in tab navigation (default: true)

Access Keys (Mnemonics)

<Menu>
    <MenuItem Header="_File">
        <MenuItem Header="_New" InputGesture="Ctrl+N" />
        <MenuItem Header="_Open" InputGesture="Ctrl+O" />
        <MenuItem Header="_Save" InputGesture="Ctrl+S" />
    </MenuItem>
    <MenuItem Header="_Edit">
        <MenuItem Header="_Cut" InputGesture="Ctrl+X" />
        <MenuItem Header="_Copy" InputGesture="Ctrl+C" />
        <MenuItem Header="_Paste" InputGesture="Ctrl+V" />
    </MenuItem>
</Menu>

<Button Content="_Submit" 
        AutomationProperties.AccessKey="Alt+S" />
Use underscore before the access key character in the content.

Keyboard Focus

public class FocusManagementExample : UserControl
{
    private Button _submitButton;

    public FocusManagementExample()
    {
        InitializeComponent();
        
        // Set initial focus
        this.AttachedToVisualTree += (s, e) =>
        {
            _submitButton?.Focus();
        };
    }

    private void OnInputComplete()
    {
        // Move focus programmatically
        var nextControl = KeyboardNavigationHandler.GetNext(
            _submitButton, 
            NavigationDirection.Next);
        nextControl?.Focus();
    }
}

Screen Reader Support

Announcing Dynamic Content

public class NotificationService
{
    public void AnnounceMessage(string message, AutomationLiveSetting priority)
    {
        var textBlock = new TextBlock
        {
            Text = message,
            [AutomationProperties.LiveSettingProperty] = priority
        };
        
        // Add to live region container
        LiveRegionContainer.Children.Add(textBlock);
    }
}

// Usage
notificationService.AnnounceMessage(
    "Form submitted successfully", 
    AutomationLiveSetting.Assertive);

notificationService.AnnounceMessage(
    "5 new messages", 
    AutomationLiveSetting.Polite);

Status Updates

<StackPanel>
    <ProgressBar Value="{Binding Progress}"
                 AutomationProperties.Name="Upload Progress" />
    
    <TextBlock AutomationProperties.LiveSetting="Polite"
               Text="{Binding ProgressText}" />
</StackPanel>
public class UploadViewModel : ViewModelBase
{
    private double _progress;
    private string _progressText;

    public double Progress
    {
        get => _progress;
        set
        {
            this.RaiseAndSetIfChanged(ref _progress, value);
            ProgressText = $"Upload {value:F0}% complete";
        }
    }

    public string ProgressText
    {
        get => _progressText;
        private set => this.RaiseAndSetIfChanged(ref _progressText, value);
    }
}

High Contrast Support

Detecting High Contrast Mode

using Avalonia.Platform;

public class ThemeService
{
    public bool IsHighContrastMode()
    {
        var platformSettings = AvaloniaLocator.Current
            .GetService<IPlatformSettings>();
        return platformSettings?.GetColorValues()?.HighContrast ?? false;
    }
}

Styling for High Contrast

<Style Selector="Button">
    <Setter Property="Background" Value="#2196F3" />
    <Setter Property="Foreground" Value="White" />
</Style>

<!-- High contrast override -->
<Style Selector="Button:highcontrast">
    <Setter Property="Background" Value="{DynamicResource SystemColorButtonFaceColor}" />
    <Setter Property="Foreground" Value="{DynamicResource SystemColorButtonTextColor}" />
    <Setter Property="BorderBrush" Value="{DynamicResource SystemColorButtonBorderColor}" />
    <Setter Property="BorderThickness" Value="1" />
</Style>

Best Practices

General Guidelines

Always provide text alternatives:
  • Set AutomationProperties.Name on images, icons, and graphics
  • Provide HelpText for complex controls
  • Label all form fields using LabeledBy or Name
Avoid common mistakes:
  • Don’t use placeholder text as the only label
  • Don’t rely on color alone to convey information
  • Don’t disable focus indicators
  • Don’t use inaccessible custom controls without proper peers

Form Accessibility

<StackPanel Spacing="10">
    <!-- Good: Label with LabeledBy -->
    <TextBlock Name="UsernameLabel" Text="Username:" />
    <TextBox AutomationProperties.LabeledBy="{Binding #UsernameLabel}"
             AutomationProperties.IsRequiredForForm="True" />

    <!-- Good: Self-labeling with Name -->
    <TextBox AutomationProperties.Name="Email Address"
             Watermark="Enter your email"
             AutomationProperties.HelpText="We'll never share your email" />

    <!-- Bad: Placeholder only -->
    <TextBox Watermark="Password" />

    <!-- Good: Error messages in live region -->
    <TextBlock Foreground="Red"
               AutomationProperties.LiveSetting="Assertive"
               Text="{Binding ValidationError}"
               IsVisible="{Binding HasError}" />
</StackPanel>

Interactive Elements

<!-- Custom interactive controls need proper ARIA roles -->
<Border Name="CustomButton"
        AutomationProperties.Name="Custom Action"
        AutomationProperties.ControlTypeOverride="Button"
        Focusable="True"
        PointerPressed="OnCustomButtonPressed"
        KeyDown="OnCustomButtonKeyDown" />

Lists and Trees

<ListBox AutomationProperties.Name="Contact List">
    <ListBoxItem AutomationProperties.PositionInSet="1"
                 AutomationProperties.SizeOfSet="10">
        <StackPanel>
            <TextBlock Text="John Doe" />
            <TextBlock Text="[email protected]" 
                       FontSize="12"
                       Foreground="Gray" />
        </StackPanel>
    </ListBoxItem>
</ListBox>

Images and Icons

<!-- Decorative image: empty name -->
<Image Source="decorative-border.png"
       AutomationProperties.Name="" />

<!-- Informative image: descriptive name -->
<Image Source="chart.png"
       AutomationProperties.Name="Sales chart showing 25% increase in Q3" />

<!-- Functional image: describe action -->
<Button>
    <Image Source="save-icon.png"
           AutomationProperties.Name="Save Document" />
</Button>

Testing Accessibility

Manual Testing

  1. Keyboard Navigation
    • Tab through all interactive elements
    • Verify logical tab order
    • Test all keyboard shortcuts
    • Ensure focus is visible
  2. Screen Reader Testing
    • Windows: NVDA or Narrator
    • macOS: VoiceOver
    • Linux: Orca
  3. High Contrast Mode
    • Enable system high contrast
    • Verify all content is visible
    • Check color contrast ratios

Automated Testing

using Avalonia.Automation.Peers;
using Xunit;

public class AccessibilityTests
{
    [Fact]
    public void Button_Should_Have_Accessible_Name()
    {
        var button = new Button { Content = "Submit" };
        var peer = button.GetValue(AutomationProperties.NameProperty);
        
        Assert.NotNull(peer);
        Assert.NotEmpty(peer);
    }

    [Fact]
    public void TextBox_Should_Be_Labeled()
    {
        var label = new TextBlock { Name = "EmailLabel", Text = "Email:" };
        var textBox = new TextBox();
        AutomationProperties.SetLabeledBy(textBox, label);
        
        var labeledBy = AutomationProperties.GetLabeledBy(textBox);
        Assert.Equal(label, labeledBy);
    }
}

Platform-Specific Considerations

Windows (UI Automation)

  • Full UIA support
  • Narrator integration
  • NVDA and JAWS support

macOS (NSAccessibility)

  • VoiceOver integration
  • Accessibility API mapping
  • Native control behavior

Linux (AT-SPI)

  • Orca screen reader support
  • AT-SPI bridge integration
  • Requires Avalonia.FreeDesktop.AtSpi package
AppBuilder.Configure<App>()
    .UsePlatformDetect()
    // Enable AT-SPI on Linux
    .UseAtSpi()
    .StartWithClassicDesktopLifetime(args);

See Also

Build docs developers (and LLMs) love