Accessibility in Xamarin.Forms

PDF for offline use:
Related Links:

Let us know how you feel about this.


0/250
Thanks for the feedback!

last updated: 2016-05

Making a Xamarin.Forms app accessible means thinking about the layout and design of many user interface elements. For some guidelines on what to consider, refer to the to the accessibility checklist for some guidelines on what to consider. Many accessibility concerns such as large fonts, and good color/contrast settings can already be addressed by Xamarin.Forms APIs.

The Android accessibility and iOS accessibility guides contain details of the native APIs exposed by Xamarin, and the UWP accessibility guide on MSDN explains the native approach on that platform. These APIs are used to fully implement accessible apps on each platform.

Xamarin.Forms does not currently have built-in support for the accessibility APIs available on each of the underlying platforms. This page provides an overview of how to support platform-specific accessibility APIs with custom code.

Describing UI Elements

Other than font, size, and color considerations, supporting screen-reader and navigation-assistance tools is the one of the most important parts of building accessible apps.

The sample code listed below includes a Xamarin.Forms Effect that can be used to expose the native accessibility APIs to better support screen-reading and navigation accessibility tools.

Review the native Android and iOS accessibility features to understand the platform-specific APIs being used, and how the sample code could be extended.

Other Accessibility Features

Other accessibility APIs (such as PostNotification on iOS) may be better suited to a DependencyService or Custom Renderer implementation.

These are not covered in this guide.

Testing Accessibility

Xamarin.Forms apps typically target multiple platforms, which means testing the accessibility features differently according to the platform. Follow these links to learn how to test for each platform:

Sample AccessibilityEffect Code

Until a future release of Xamarin.Forms exposes accessibility APIs directly, the Xamarin.Forms effect code below can be used (and expanded upon) to build accessible applications using the current version of Xamarin.Forms.

About the Sample Code

Creating a Xamarin.Forms effect requires code in the common project (to define the API) and implementations in each platform-specific project:

  • AccessibilityEffect – this class defines the API for the effect and belongs in the PCL or Shared Project.
  • How to use the effect in XAML and C# – these examples show how to use the effect on Xamarin.Forms controls.
  • iOS Implementation – iOS platform-specific code that applies the settings to native controls.
  • Android Implementation – Android platform-specific code that applies the settings to native controls.

The API shown in the example below can be expanded to provide support for additional accessibility features.

Common Code (PCL or Shared)

This code belongs in the shared library containing the Xamarin.Forms application and user interface.

AccessibilityEffect

The AccessibilityEffect defines the API for adding accessible elements to Xamarin.Forms controls. The API chosen for the sample consists of

  • AccessibilityLabel – string property to add text for screen readers.
  • InAccessibleTree – boolean that tells screen readers to 'skip' this control (when set to false).

These two properties were chosen as they are common across the different platforms' accessibility APIs. The AccessbilityEffect class implements the bindable properties and the code for adding them to a control (platform-specific implementations for the effect are required to make it work):

public static class AccessibilityEffect
{
  public static readonly BindableProperty AccessibilityLabelProperty =
    BindableProperty.CreateAttached("AccessibilityLabel",
        typeof(string), typeof(AccessibilityEffect), "",
        propertyChanged:OnAccessibilityLabelChanged);
  public static readonly BindableProperty InAccessibleTreeProperty =
    BindableProperty.CreateAttached("InAccessibleTree",
        typeof(bool), typeof(AccessibilityEffect), true,
        propertyChanged: OnInAccessibleTreeChanged);

  // AccessibilityLabel
  public static string GetAccessibilityLabel(BindableObject view)
  {
    return (string)view.GetValue (AccessibilityLabelProperty);
  }
    public static void SetAccessibilityLabel(BindableObject view, string value)
  {
    view.SetValue (AccessibilityLabelProperty, value);
  }
  static void OnAccessibilityLabelChanged(BindableObject bindable, object oldValue, object newValue)
  {
    var hasLabel = !string.IsNullOrEmpty(newValue as string);
    AddRemoveEffect(bindable, hasLabel);
  }
  // InAccessibleTree
  public static bool GetInAccessibleTree(BindableObject view)
  {
    return (bool)view.GetValue(InAccessibleTreeProperty);
  }
  public static void SetInAccessibleTree(BindableObject view, bool value)
  {
    view.SetValue(InAccessibleTreeProperty, value);
  }
  static void OnInAccessibleTreeChanged(BindableObject bindable, object oldValue, object newValue)
  {
    var add = newValue != null;
    AddRemoveEffect(bindable, add);
  }
  // .. other properties
  // helper method
  static void AddRemoveEffect(BindableObject bindable, bool add)
  {
    var view = bindable as View;
    if (view == null)
    {
      return;
    }
    if (add)
    {
      if (view.Effects.Count == 0)
      {
        // shortcut to add if there are none already
        view.Effects.Add(new AddAccessibilityEffect());
      }
      else
      {
        // more expensive check to see if it exists before adding
        var exists = view.Effects.First(e => e is AddAccessibilityEffect);
        if (exists == null)
        {
          view.Effects.Add(new AddAccessibilityEffect());
        }
      }
    }
    else
    {
      var toRemove = view.Effects.FirstOrDefault(e => e is AddAccessibilityEffect);
      if (toRemove != null)
      {
        view.Effects.Remove(toRemove);
      }
    }
  }
  public class AddAccessibilityEffect : RoutingEffect
  {
    // string identifier matches [assembly] attributes in the platform-specific projects
    public AddAccessibilityEffect() : base("MyCompany.AddAccessibilityEffect")
    {
    }
  }
}

Use in XAML

The C# code below shows a label and entry control. The label control is excluded from the accessibility tree (i.e. it will be ignored by screen readers) and the entry control is assigned an accessible label that would be read by a screen-reader:

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:a="clr-namespace:MyApp;assembly=MyAppAssembly"
    x:Class="MyApp.MyPageXaml"
    Padding="20">
        <StackLayout VerticalOptions="StartAndExpand">
        <Label Text="Name"
                a:AccessibilityEffect.InAccessibleTree="false" />
        <Entry Text="{Binding Path=Name}" x:Name="nameEntry"
                Placeholder="task name"
                a:AccessibilityEffect.AccessibilityLabel="Todo item"
                />
    </StackLayout>
</ContentPage>

Note: the effect can only be used in the XAML if a custom xmlns is declared that creates a reference to the assembly containing the effect. In this example, xmlns:a has been declared to reference the local assembly, and the a: prefix is used to add the effect attributes to XAML controls.

Use in C

The C# code below shows a label and an entry control. The label control is excluded from the accessibility tree (i.e. it will be ignored by screen readers) and the entry control is assigned an accessible label that would be read by a screen-reader:

var nameLabel = new Label { Text = "Name" };
var nameEntry = new Entry { StyleId = "TodoName", Placeholder="task name" };
nameEntry.SetBinding (Entry.TextProperty, "Name");
AccessibilityEffect.SetInAccessibleTree(nameLabel, false); // hide from screenreader
AccessibilityEffect.SetAccessibilityLabel(nameEntry, "Todo item"); // screenreader description

Android Platform-Specific Effect

The following class should be added to the Android application project. It implements the effect API (AccessibilityLabel and InAccessibleTree) using the native Android widget properties ContentDescription and Focusable to provide an accessible UI.

The "MyCompany.AddAccessibilityEffect" string in the RoutingEffect subclass matches the ResolutionGroupName and ExportEffect attributes – this is how the effect is resolved and applied at runtime.

using System;
using Android.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(AddAccessibilityEffect), "AddAccessibilityEffect")]
namespace MyApp
{
    /// <summary>Add accessibility properties to Xamarin.Forms controls in Android</summary>
    public class AddAccessibilityEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            try
            {
                Control.ContentDescription = AccessibilityEffect.GetAccessibilityLabel(Element);
                Control.Focusable = AccessibilityEffect.GetInAccessibleTree(Element);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
            }
        }

        protected override void OnDetached()
        {
        }

        protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args)
        {
            if (args.PropertyName == AccessibilityEffect.AccessibilityLabelProperty.PropertyName)
            {
                Control.ContentDescription = AccessibilityEffect.GetAccessibilityLabel(Element);
            }
            else if (args.PropertyName == AccessibilityEffect.InAccessibleTreeProperty.PropertyName)
            {
                Control.Focusable = AccessibilityEffect.GetInAccessibleTree(Element);
            }
        }
    }
}

iOS Platform-Specific Effect

This class should be added to the iOS application project. It implements the effect API (AccessibilityLabel and InAccessibleTree) using the native iOS view properties AccessibilityLabel and IsAccessibilityElement to provide an accessible UI.

The "MyCompany.AddAccessibilityEffect" string in the RoutingEffect subclass matches the ResolutionGroupName and ExportEffect attributes – this is how the effect is resolved and applied at runtime:

using System;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ResolutionGroupName("MyCompany")]
[assembly: ExportEffect(typeof(AddAccessibilityEffect), "AddAccessibilityEffect")]
namespace MyApp
{
    /// <summary>Add accessibility properties to Xamarin.Forms controls in iOS</summary>
    public class AddAccessibilityEffect : PlatformEffect
    {
        protected override void OnAttached()
        {
            try
            {
                Control.AccessibilityLabel = AccessibilityEffect.GetAccessibilityLabel(Element);
                // other properties
            }
            catch (Exception ex)
            {
                Console.WriteLine("Cannot set property on attached control. Error: ", ex.Message);
            }
        }

        protected override void OnDetached()
        {
        }

        protected override void OnElementPropertyChanged(System.ComponentModel.PropertyChangedEventArgs args)
        {
            if (args.PropertyName == AccessibilityEffect.AccessibilityLabelProperty.PropertyName)
            {
                Control.AccessibilityLabel = AccessibilityEffect.GetAccessibilityLabel(Element);
            }
            else if (args.PropertyName == AccessibilityEffect.InAccessibleTreeProperty.PropertyName)
            {
                Control.IsAccessibilityElement = AccessibilityEffect.GetInAccessibleTree(Element);
            }
            // ... other properties
            else
            {
                base.OnElementPropertyChanged(args);
            }
        }
    }
}

Windows

Platform-specific implementations can also be written for Windows, Windows Phone, and UWP projects using the native accessibility APIs.

Summary

Xamarin.Forms does not currently support some accessibility APIs available in the underlying native frameworks. This guide includes sample code for a Xamarin.Forms effect to help developers build accessible user interfaces by setting the accessibility properties on the native controls.

Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.