The screen reader support APIs are agnostic of the UI system, so they work with UI Toolkit, uGUI, custom UI frameworks, and non-UI content such as 2D or 3D objectsA 3D GameObject such as a cube, terrain or ragdoll. More info
See in Glossary in the game world. For simplicity, this guide uses UI Toolkit, but you can adapt the code to your UI framework of choice.
This example illustrates how to create an accessibility node, connect it to a UI Toolkit button, and test it with platform screen readers. By the end, you’ll have a button that native screen readers can read and activate.
This guide is for developers familiar with the Unity Editor, UI Toolkit, and C# scripting. Before you start, get familiar with the following:
The Accessibility module is enabled by default. If for some reason it’s not enabled in your project, do the following to enable it:
Use UI Toolkit to create a Start Game button in your sceneA Scene contains the environments and menus of your game. Think of each unique Scene file as a unique level. In each Scene, you place your environments, obstacles, and decorations, essentially designing and building your game in pieces. More info
See in Glossary.
Create a project with any template.
Create a UXML file named AccessibleStartMenu.uxml with the following content:
<ui:UXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:Button text="Start Game" name="startButton"/>
</ui:UXML>
Create a C# script named AccessibleStartMenu.cs with the following content:
using UnityEngine;
using UnityEngine.UIElements;
public class AccessibleStartMenu : MonoBehaviour
{
Button m_Button;
void OnEnable()
{
VisualElement root = GetComponent<UIDocument>().rootVisualElement;
m_Button = root.Q<Button>("startButton");
m_Button.clicked += OnButtonClicked;
}
void OnDisable()
{
m_Button.clicked -= OnButtonClicked;
}
void OnButtonClicked()
{
Debug.Log("Start Game button clicked");
}
}
The accessibility hierarchy is a semantic representation of your UI that screen readers use to discover and interact with your content. Screen readers cannot detect GameObject components or UI elements directly. They rely on this hierarchy to navigate your application. You create an AccessibilityHierarchy, then add an AccessibilityNode that represents the Start Game button.
To create the accessibility hierarchy:
UnityEngine.Accessibility namespace.AccessibilityHierarchy instance.AccessibilityNode to the accessibility hierarchy.label, role, and state properties of the node according to the button’s text and interactable state.// ...
using UnityEngine.Accessibility;
public class AccessibleStartMenu : MonoBehaviour
{
// ...
AccessibilityHierarchy m_AccessibilityHierarchy;
AccessibilityNode m_AccessibilityNode;
void OnEnable()
{
// ...
CreateAccessibilityHierarchy();
}
// ...
void CreateAccessibilityHierarchy()
{
// Create a new accessibility hierarchy.
m_AccessibilityHierarchy = new AccessibilityHierarchy();
// Create a new accessibility node with the button's text as the label
// (what the screen readers announces).
m_AccessibilityNode = m_AccessibilityHierarchy.AddNode(m_Button.text);
// Set a semantic role (tells the screen reader this is a button).
m_AccessibilityNode.role = AccessibilityRole.Button;
// Set the state (is it currently interactable?).
m_AccessibilityNode.state = m_Button.enabledSelf ?
AccessibilityState.None : AccessibilityState.Disabled;
}
}
To set the node’s screen coordinates:
frame property to the calculated screen rectangle.Update the AccessibleStartMenu.cs script as below:
public class AccessibleStartMenu : MonoBehaviour
{
// ...
void OnEnable()
{
// ...
m_Button.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
}
void OnDisable()
{
// ...
m_Button.UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
}
// ...
void OnGeometryChanged(GeometryChangedEvent evt)
{
Rect worldRect = m_Button.worldBound;
float scale = m_Button.panel.scaledPixelsPerPoint;
// Update the screen coordinates of the node.
m_AccessibilityNode.frame =
new Rect(worldRect.position * scale, worldRect.size * scale);
}
}
Subscribe to the node’s invoked event, which is triggered when the user activates the node via the screen reader, then invoke the button’s NavigationSubmitEvent in the event handler.
Update the CreateAccessibilityHierarchy method in the AccessibleStartMenu.cs script as below:
public class AccessibleStartMenu : MonoBehaviour
{
// ...
void CreateAccessibilityHierarchy()
{
// ...
// Handle when the user activates this node (e.g., double-tap).
// Called `selected` in versions before Unity 6.3.
m_AccessibilityNode.invoked += () =>
{
using var evt = NavigationSubmitEvent.GetPooled();
evt.target = m_Button;
m_Button.SendEvent(evt);
return true;
};
}
}
AssistiveSupport.activeHierarchy.
AssistiveSupport.activeHierarchy is automatically set to null to free resources.AssistiveSupport.activeHierarchy to null.public class AccessibleStartMenu : MonoBehaviour
{
// ...
void OnEnable()
{
// ...
AssistiveSupport.activeHierarchy = m_AccessibilityHierarchy;
AssistiveSupport.screenReaderStatusChanged += OnScreenReaderStatusChanged;
}
void OnDisable()
{
// ...
AssistiveSupport.activeHierarchy = null;
AssistiveSupport.screenReaderStatusChanged -= OnScreenReaderStatusChanged;
}
// ...
void OnScreenReaderStatusChanged(bool enabled)
{
if (enabled)
{
AssistiveSupport.activeHierarchy = m_AccessibilityHierarchy;
}
// else
// {
// // This is automatically done when the user turns the screen
// // reader off.
// AssistiveSupport.activeHierarchy = null;
// }
}
}
You created a semantic representation (AccessibilityNode) of the visual button that screen readers can discover and interact with.
The complete AccessibleStartMenu.cs script is as follows:
using UnityEngine;
using UnityEngine.Accessibility;
using UnityEngine.UIElements;
public class AccessibleStartMenu : MonoBehaviour
{
Button m_Button;
AccessibilityHierarchy m_AccessibilityHierarchy;
AccessibilityNode m_AccessibilityNode;
void OnEnable()
{
VisualElement root = GetComponent<UIDocument>().rootVisualElement;
m_Button = root.Q<Button>("startButton");
m_Button.clicked += OnButtonClicked;
m_Button.RegisterCallback<GeometryChangedEvent>(OnGeometryChanged);
CreateAccessibilityHierarchy();
AssistiveSupport.activeHierarchy = m_AccessibilityHierarchy;
AssistiveSupport.screenReaderStatusChanged += OnScreenReaderStatusChanged;
}
void OnDisable()
{
m_Button.clicked -= OnButtonClicked;
m_Button.UnregisterCallback<GeometryChangedEvent>(OnGeometryChanged);
AssistiveSupport.activeHierarchy = null;
AssistiveSupport.screenReaderStatusChanged -= OnScreenReaderStatusChanged;
}
void CreateAccessibilityHierarchy()
{
// Create a new accessibility hierarchy.
m_AccessibilityHierarchy = new AccessibilityHierarchy();
// Create a new accessibility node with the button's text as the label
// (what the screen readers announces).
m_AccessibilityNode = m_AccessibilityHierarchy.AddNode(m_Button.text);
// Set a semantic role (tells the screen reader this is a button).
m_AccessibilityNode.role = AccessibilityRole.Button;
// Set the state (is it currently interactable?).
m_AccessibilityNode.state = m_Button.enabledSelf ?
AccessibilityState.None : AccessibilityState.Disabled;
// Handle when the user activates this node (e.g., double-tap).
// Called `selected` in versions before Unity 6.3.
m_AccessibilityNode.invoked += () =>
{
using var evt = NavigationSubmitEvent.GetPooled();
evt.target = m_Button;
m_Button.SendEvent(evt);
return true;
};
}
void OnGeometryChanged(GeometryChangedEvent evt)
{
Rect worldRect = m_Button.worldBound;
float scale = m_Button.panel.scaledPixelsPerPoint;
// Update the screen coordinates of the node.
m_AccessibilityNode.frame =
new Rect(worldRect.position * scale, worldRect.size * scale);
}
void OnButtonClicked()
{
Debug.Log("Start Game button clicked");
}
void OnScreenReaderStatusChanged(bool isEnabled)
{
if (isEnabled)
{
AssistiveSupport.activeHierarchy = m_AccessibilityHierarchy;
}
// else
// {
// // This is automatically done when the user turns the screen
// // reader off.
// AssistiveSupport.activeHierarchy = null;
// }
}
}
To attach the script to your scene:
GameObject in your scene and name it AccessibleStartMenu.UI Document component to the GameObject.Panel Settings field in the InspectorA Unity window that displays information about the currently selected GameObject, asset or project settings, allowing you to inspect and edit the values. More infoUI Document component.AccessibleStartMenu.uxml file to the Source Asset field.AccessibleStartMenu.cs script to the GameObject.To test the hierarchy and node properties in the Unity Editor:
To test screen reader interaction on your target platform: