Solutions to some common accessibility issues found in XAML apps

My recent post, Learn about cool things you can do with UI Automation, and help teams around you at the same time, discussed how as part you learning how to use the super-useful Windows UI Automation (UIA) API, you could write code which looks for accessibility issues in apps built by teams near you. By doing that, you're helping those teams reach more customers, because their UI will be accessible to everyone. The code snippet I included in that post related to finding elements with no accessible names, buttons which aren't programmatically actionable, and text which might not be speakable by a screen reader. If you do find XAML apps which exhibit these issues, some possible solutions for resolving these issues are described below.

 

Controls with no accessible names
 
If a user reaches an element with no name, then often they will have no way of knowing what the element relates to. For example, if it's an Edit field on a form, which field is it? Or if it's a Button, what will happen if it's invoked? 

 
Solutions
 
Always use a localized string for the accessible names of your controls. The language of the accessible name should match the language of text shown visually in the UI.
 
1. If the accessible name of your control doesn't change, set the AutomationProperties.Name property in the control's XAML to be some appropriate localized string.

    AutomationProperties.Name="{CustomResource <your localized string id>}"

Note: Sometimes the element of interest has a Style applied to it, and the accessible name will need to be set on an element in a Style's Template, not directly on the element which uses that Style.
 
2. For dynamic accessible names, the name can be bound to a view model property, in a similar way to how visual text is bound to the view model.

    AutomationProperties.Name="{Binding <your view model's accessible name property>}"
 
3. If the accessible name provided to the user comes from the property of a Style applied to your control, then you should update the style setters to include the appropriate accessible name.

    <Setter Property="AutomationProperties.Name " Value="{CustomResource <your localized sting id>}"/>

 

The above solutions apply to typical situations where a control should have an accessible name added to it. It's worth noting that just because an Edit control shows text, doesn't mean the control has a name. An Edit control might be showing text of (say) "200" and a screen reader would speak that text when the user tabs to the control. But if the Edit control has no name, it can be difficult for the user to know what that value refers to.
 
 
 
4.  In some cases, it would be preferable for the user if the control with no name was not exposed by your UI at all. For example, if you’ve built some custom UI where a Button contains another Button and a Text element, exposing the contained Button and Text elements might not be helpful to your customers. Rather, all the information and functionality required by your user can be accessed from the containing Button.
 
One option here would be to move the child elements to the UIA "Raw view". (Details on that are discussed below.) But while doing that would make the nameless button less commonly hit, the user can still reach it. As such, the button would still need an accessible name applied.

A preferred solution would be to remove those child elements from all views of the UIA tree. This can be done by creating a custom AutomationPeer for the parent Button which explicitly hides its child elements from UIA. Information on creating custom AutomationPeers can be found at Custom AutomationPeers.

For example:

(i) Create a custom AutomationPeer for the button, which will provide all the default accessible functionality for a Button, except for saying that the button has no children.

    class CustomButtonAutomationPeer : Windows.UI.Xaml.Automation.Peers.ButtonAutomationPeer
    {
        public CustomButtonAutomationPeer(Button owner) : base(owner) {}
        protected override IList<AutomationPeer> GetChildrenCore()
        {
            return null;
        }
    }

(ii) Create a custom class for the button which uses your new custom AutomationPeer.

    public class AutomationButton : Button
    {
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new CustomButtonAutomationPeer(this);
        }
    }
 
(iii) In XAML, reference your custom class instead of the usual "Button" class.

    <controls:AutomationButton ... />

 
 
Buttons which aren't actionable programmatically
 
While a button might work fine with touch, mouse and the keyboard, it must also be programmatically invokable if a Narrator user is to be able to invoke it via touch. As MSDN states, an element that declares itself to be a Button, must support at least one of the following operations programmatically; Invoke, Toggle, Expand.

 
 
Solutions
 
1. Use a standard control instead of a custom control. All standard controls have an associated AutomationPeer which makes the control accessible. For example, a Button has a ButtonAutomationPeer, and that allows the Button to be programmatically invokable. If your UI has a custom control which is exposed through UIA as a control of type "Button", then it might not support being programmatically invoked. Consider replacing your custom control with a standard Button control.
 
2. If you use a custom control, derive the control from a standard control. If a custom control derives from a Button control, it will automatically have a ButtonAutomationPeer associated with it. As such, the control can be programmatically invoked.
 
3. If you use a custom control which does not derive from a control which can be programmatically invoked, add support for the invoke action to the control. For example:

(i) Add a custom AutomationPeer for the control. The code below assumes that the custom control is derived from FrameworkElement. (A FrameworkElement is not programmaticaly invokable.)

    class MyCustomControlAutomationPeer :
        Windows.UI.Xaml.Automation.Peers.FrameworkElementAutomationPeer,
        IInvokeProvider // This custom AutomationPeer supports programmatic invoking.
    {
        public CustomMapAutomationPeer(FrameworkElement owner) : base(owner) {}

        // UIA calls this when it wants to know what actions the control associated with this custom
        // AutomationPeer supports.

        protected override object GetPatternCore(PatternInterface patternInterface)
        {
            if (patternInterface == PatternInterface.Invoke)
            {
                return this;
            }

            return base.GetPatternCore(patternInterface);
        }

        // The control is being programmatically invoked.
        public void Invoke()
        {
            // Take whatever action usually happens when the control is clicked, tapped or invoked
            // via the keyboard...
        }
    }

(ii) When UIA asks the custom control for an AutomationPeer to provide the accessibility for the control, return an instance of the custom AutomationPeer which supports being programmatically invoked.

    public sealed partial class MyCustomControl : UserControl
    {
        protected override AutomationPeer OnCreateAutomationPeer()
        {
            return new MyCustomControlAutomationPeer(this);
        }

Note that the above code focuses on controls which need to support being programmatically invoked. In some cases it's more appropriate for the buttons to support being toggled or expanded/collapsed. The approach taken in each case is similar to how support for the invoke action is added. For more information on how controls expose behaviors to UIA, see Control Patterns and Interfaces.
 
 
Text controls with unspeakable names
 
Often the graphics shown inside a Button control are implemented in the form of a Text element containing a glyph. If a user reaches that Text element and a custom accessible name has not been set on it, then a screen reader will attempt to speak the glyph character. This will often result in no audio at all, and the user will not know the purpose of the element that they've reached.

Solutions
 
1. Exposing the glyph as its own element may not be helpful to the user. Rather, the Button that contains the glyph provides all the information and functionality that the user needs. In that case, move the Text element out of the UIA Control view and into the UIA Raw view. Screen reader users will less frequently reach elements that are in the UIA Raw view. In the XAML defining the Text element, add the following:

    AutomationProperties.AccessibilityView="Raw"

Note: Sometimes the element of interest has a Style applied to it, and so the above XAML will need to be set on an element in a Style's Template, not directly on the element which uses that Style.
 
2. Depending on the use of Styles, if you wish to move the Text element to the UIA Raw view then you may be necessary to update the style setters to include the appropriate accessible view, rather than setting the property directly on an element.

    <Setter Property="AutomationProperties.AccessibilityView" Value="Raw"/>

 
3. If you decide that it is beneficial for Text element presenting the glyph to be exposed to the user in the UIA Control view, then a useful localized accessible name needs to be set on the element. This can be done with one of the solutions listed in the "Controls with no accessible names" section above. While those solutions discuss adding names to elements that don't have names, the same solutions apply to setting a useful accessible name on elements whose default accessible name is not helpful to the user.