Skip to content

Accessibility

Vetle444 edited this page Nov 12, 2025 · 3 revisions

Accessibility

Making your app accessible ensures that all users, including those with disabilities, can effectively use your application. Accessibility features work with platform screen readers like VoiceOver (iOS) and TalkBack (Android).

Table of Contents


MAUI SemanticProperties

.NET MAUI provides SemanticProperties which are attached properties that define information about which controls should receive accessibility focus and which text should be read aloud to the user. These properties set platform accessibility values so that a screen reader can speak about the element.

Description

The SemanticProperties.Description attached property represents a short, descriptive text that a screen reader uses to announce an element. This property should be set for elements that have a meaning that's important for understanding the content or interacting with the user interface.

Usage:

<Image Source="dotnet_bot.png"
       SemanticProperties.Description="Cute dot net bot waving hi to you!" />

Or in C#:

Image image = new Image { Source = "dotnet_bot.png" };
SemanticProperties.SetDescription(image, "Cute dot net bot waving hi to you!");

⚠️ Critical behavior: Description automatically excludes children!

When you set SemanticProperties.Description on a parent element that has children, all child elements are automatically excluded from the accessibility tree. Screen readers will only announce the custom description you provide, and users won't be able to navigate to individual children.

This is the native behavior on both iOS and Android, and developers need to be aware of this when setting descriptions!

Example showing this behavior:
<!-- Without Description: Each label is individually accessible -->
<VerticalStackLayout>
    <Label Text="John Doe" />
    <Label Text="Born: 1980-05-15" />
    <Label Text="+47 123 45 678" />
</VerticalStackLayout>
<!-- Result: 3 separate swipe gestures required -->

<!-- With Description: Children are automatically excluded -->
<VerticalStackLayout SemanticProperties.Description="John Doe, Born: 1980-05-15, Phone: +47 123 45 678">
    <Label Text="John Doe" />
    <Label Text="Born: 1980-05-15" />
    <Label Text="+47 123 45 678" />
</VerticalStackLayout>
<!-- Result: Only 1 swipe gesture, reads the custom description -->

Important notes:

  • Avoid setting Description on a Label. This will stop the Text property being spoken by the screen reader. The visual text should ideally match the text read aloud.
  • Avoid setting Description on an Entry or Editor on Android. This will stop TalkBack actions from functioning. Instead, use the Placeholder property or the Hint attached property.
  • Be aware that setting Description on a parent with children will automatically exclude all children from accessibility navigation. This is useful for grouping information but can be problematic if you have interactive elements as children.

Hint

The SemanticProperties.Hint attached property provides additional context to the Description, such as the purpose of a control.

Usage:

<Image Source="like.png"
       SemanticProperties.Description="Like"
       SemanticProperties.Hint="Like this post." />

Or in C#:

Image image = new Image { Source = "like.png" };
SemanticProperties.SetDescription(image, "Like");
SemanticProperties.SetHint(image, "Like this post.");

HeadingLevel

The SemanticProperties.HeadingLevel attached property enables an element to be marked as a heading to organize the UI and make it easier to navigate.

Usage:

<Label Text="Main Heading"
       SemanticProperties.HeadingLevel="Level1" />

Or in C#:

Label label = new Label { Text = "Main Heading" };
SemanticProperties.SetHeadingLevel(label, SemanticHeadingLevel.Level1);

Available heading levels: None, Level1, Level2, Level3, Level4, Level5, Level6, Level7, Level8, Level9

Note: Not all platforms support all heading levels. Check the specific platform documentation for details.


MAUI AutomationProperties

In addition to SemanticProperties, MAUI provides AutomationProperties for more advanced accessibility control. These are attached properties that can be added to any element to indicate how the element is reported to the underlying platform's accessibility framework.

ExcludedWithChildren

The AutomationProperties.ExcludedWithChildren property determines if an element and its children should be excluded from the accessibility tree.

<StackLayout AutomationProperties.ExcludedWithChildren="true">
    <!-- Children will not be visible to screen readers -->
</StackLayout>

IsInAccessibleTree

The AutomationProperties.IsInAccessibleTree property indicates whether the element is available in the accessibility tree. Must be set to true to use other automation properties.

<Entry AutomationProperties.IsInAccessibleTree="true" />

Warning: On iOS, if IsInAccessibleTree is true on any control that has children, the screen reader will be unable to reach the children.


DIPS.Mobile.UI Accessibility Mode

DIPS.Mobile.UI provides an Accessibility.Mode attached property that simplifies a common accessibility scenario for screen readers. This implementation uses MAUI's SemanticProperties.Description under the hood to automatically group child elements - leveraging the native behavior where setting a description excludes children from the accessibility tree.

GroupChildren Mode

The GroupChildren mode automatically collects text from all child elements and sets a combined SemanticProperties.Description on the parent. This leverages the native behavior where setting a description automatically excludes children, effectively grouping all information into a single screen reader announcement.

When to use:

  • Multiple child labels or text elements that form a single logical piece of information (e.g., address blocks, contact information, patient cards, product details)
  • Read-only informational displays where all content should be announced together
  • Card-like UI patterns where related information should be grouped
  • When you want to reduce the number of swipe gestures needed for screen reader users

When NOT to use:

  • When any child element is interactive (Button, Entry, Switch, etc.) - interactive elements need individual focus for usability
  • When child elements represent separate, unrelated pieces of information that users might want to navigate individually
  • When children have complex accessibility requirements or need custom hints/traits
  • In lists or collections where each item should be individually navigable

Example:

<dui:VerticalStackLayout Spacing="{dui:Sizes size_1}"
                         Padding="{dui:Sizes size_3}"
                         BackgroundColor="{dui:Colors color_surface_subtle}"
                         dui:Accessibility.Mode="GroupChildren">
    <dui:Label Text="John Doe"
               Style="{dui:Styles Label=Body400}" />
    <dui:Label Text="Born: 1980-05-15"
               Style="{dui:Styles Label=UI200}" />
    <dui:HorizontalStackLayout Spacing="{dui:Sizes size_1}">
        <dui:Label Text="Phone:"
                   Style="{dui:Styles Label=UI200}" />
        <dui:Label Text="+47 123 45 678"
                   Style="{dui:Styles Label=UI200}" />
    </dui:HorizontalStackLayout>
    <dui:Label Text="[email protected]"
               Style="{dui:Styles Label=UI200}" />
</dui:VerticalStackLayout>

Result: VoiceOver/TalkBack will read: "John Doe, Born: 1980-05-15, Phone:, +47 123 45 678, [email protected]"

Without this mode, screen readers would require 5 separate swipe gestures to read all information. With GroupChildren, it's read in one focus.

How it works:

  1. Automatically collects text from all descendant Label elements
  2. Collects any existing SemanticProperties.Description values from descendants
  3. Combines all text with commas: "Text1, Text2, Text3"
  4. Sets the combined text as SemanticProperties.Description on the parent
  5. Because Description is set, children are automatically excluded by the native platform behavior

Technical notes:

  • This mode does not track runtime changes - it collects text once when the layout is rendered
  • If text in child labels changes after rendering, the accessibility description won't update
  • If you add/remove children dynamically, the description won't reflect those changes
  • The implementation relies on the native platform behavior where SemanticProperties.Description excludes children

Best Practices

Best Practices

  1. Make your UI self-describing: Test that all elements are screen reader accessible. Add descriptive text and hints when necessary.

  2. Provide alternate text for images and icons: Always use SemanticProperties.Description for meaningful images.

  3. Group related information: Use Accessibility.Mode="GroupChildren" for card-like patterns with multiple labels representing a single piece of information.

  4. Exclude decorative elements: Use AutomationProperties.ExcludedWithChildren="true" for purely decorative elements, or set a custom SemanticProperties.Description on the parent (which automatically excludes children).

  5. Don't mix visual and semantic text: If you set a custom SemanticProperties.Description, ensure it matches the visual content users can see.

  6. Test with actual screen readers: Enable VoiceOver (iOS) or TalkBack (Android) and navigate through your app to ensure a smooth experience.

  7. Support large fonts and high contrast: Use dynamic layouts that can accommodate larger text sizes.

  8. Localize accessibility descriptions: When your app supports multiple languages, ensure all accessibility text is also localized.

  9. Keep interactive elements separate: Don't use GroupChildren on containers with buttons, switches, or other interactive controls.

  10. Follow WCAG guidelines: Ensure your app is perceivable, operable, understandable, and robust for all users. See Web Content Accessibility Guidelines (WCAG).

  11. Choose the right approach:

    • For custom descriptions: Use SemanticProperties.Description directly (remembers it excludes children!)
    • For automatic grouping: Use DIPS.Mobile.UI's Accessibility.Mode="GroupChildren" which automatically collects and combines text
    • For excluding decorative elements: Use AutomationProperties.ExcludedWithChildren="true"
  12. Understand the Description behavior: Always remember that SemanticProperties.Description automatically excludes children from accessibility navigation. Don't set it on parents with interactive children (buttons, text fields, etc.).

  13. Always set Description with Touch.Command: When using the Touch effect to make elements interactive, always set SemanticProperties.Description so screen readers announce the element as a "Button". See details in Touch Effect Accessibility below.


Touch Effect Accessibility

When using the Touch effect (from Touch) to make elements interactive, proper accessibility support is critical. The Touch effect automatically enhances accessibility when SemanticProperties.Description is set.

How it works

When you set SemanticProperties.Description on an element with Touch.Command, the Touch effect automatically configures the native platform accessibility traits:

  • iOS: Appends UIAccessibilityTrait.Button to the element's accessibility traits
  • Android: Sets the accessibility class name to "android.widget.Button"

This ensures screen readers (VoiceOver/TalkBack) announce the element as a "Button", providing clear feedback about its interactive nature.

Example without accessibility (❌ Bad):

<Grid dui:Touch.Command="{Binding SelectPatientCommand}">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    
    <VerticalStackLayout>
        <Label Text="John Doe" />
        <Label Text="Born: 1980-05-15" />
    </VerticalStackLayout>
    
    <Label Grid.Column="1" Text="" />
</Grid>

Problem: Screen reader users won't know this element is tappable. They'll hear each label separately without any indication it's an interactive button.

Example with accessibility (✅ Good):

<Grid dui:Touch.Command="{Binding SelectPatientCommand}"
      SemanticProperties.Description="Select patient John Doe, Born 1980-05-15">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    
    <VerticalStackLayout>
        <Label Text="John Doe" />
        <Label Text="Born: 1980-05-15" />
    </VerticalStackLayout>
    
    <Label Grid.Column="1" Text="" />
</Grid>

Result: Screen readers will announce: "Select patient John Doe, Born 1980-05-15, Button" - users know it's interactive!

Note: Remember that setting SemanticProperties.Description on the Grid excludes the child Labels from accessibility (see Description behavior).

Alternative using GroupChildren mode (✅ Also Good):

If you want to automatically combine the text from children without manually writing the description:

<Grid dui:Touch.Command="{Binding SelectPatientCommand}"
      dui:Accessibility.Mode="GroupChildren">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    
    <VerticalStackLayout>
        <Label Text="John Doe" />
        <Label Text="Born: 1980-05-15" />
    </VerticalStackLayout>
    
    <Label Grid.Column="1" Text="" />
</Grid>

Result: The GroupChildren mode automatically collects text from all Labels and sets SemanticProperties.Description to "John Doe, Born: 1980-05-15, →". The Touch effect then adds the Button trait, so screen readers announce: "John Doe, Born: 1980-05-15, →, Button"

This approach is convenient when you have dynamic content or don't want to manually maintain the description text. See GroupChildren Mode for more details.

Important notes:

  • ⚠️ Always set SemanticProperties.Description when using Touch.Command on non-button elements
  • The Touch effect monitors for changes to SemanticProperties.Description and updates accessibility traits automatically
  • Remember that setting Description on a parent excludes children from accessibility (see Description behavior)

Enabling Screen Readers

iOS - VoiceOver

  1. Open the Settings app
  2. Select Accessibility > VoiceOver
  3. Turn VoiceOver on

For more information: Apple VoiceOver Guide

Android - TalkBack

  1. Open the Settings app
  2. Select Accessibility > TalkBack
  3. Turn Use TalkBack on

For more information: Google TalkBack Guide


Additional Resources

Clone this wiki locally