Use accessibility announcements
On Android, you can post an accessibility message by using the AccessibilityManager object. Create an AccessibilityEvent, set the type to AccessibilityEvent.TYPE_ANNOUNCEMENT and supply a message.
kotlin
val type = AccessibilityEventCompat.TYPE_ANNOUNCEMENT
val event = AccessibilityEvent.obtain(type)
event.text.add("Appt announcement")
event.className = Context::class.java.name
event.packageName = packageName
val accessibilityManager = ContextCompat.getSystemService(this, AccessibilityManager::class.java)
accessibilityManager?.sendAccessibilityEvent(event) In Jetpack Compose, to notify Composable state changes, you can use the liveRegion.liveRegion()) property from semantics.semantics(kotlin.Boolean,kotlin.Function1)) block modifier.
You can choose from two options for liveRegion:
LiveRegionMode.Polite) , which waits for the speech announcement in progress to complete
LiveRegionMode.Assertive), which interrupts ongoing speech to immediately announce changes
If you don't specify the liveRegion property, it indicates to Compose that updates to this field would not be announced.
kotlin
var changingText by remember{ mutableStateOf("Changing text") }
Text(
text = changingText,
modifier = Modifier.semantics {
liveRegion = LiveRegionMode.Polite
contentDescription = changingText // workaround for bug
}
) On iOS, you post an accessibility announcement by using the UIAccessibility object. The post method can be used to post data to assistive technologies. Set the type to announcement and supply a string argument to announce something.
You can also supply an NSAttributedString to customize the behavior of the announcement. For example, accessibilitySpeechQueueAnnouncement can be used to queue an announcement, instead of announcing it immediately. For more options, see: accessibility attribute keys of NSAttributedString.
swift
// Using String
UIAccessibility.post(
notification: .announcement,
argument: "Appt announcement"
)
// Using NSAttributedString
let message = NSAttributedString(
string: "Appt customized announcement",
attributes: [
.accessibilitySpeechQueueAnnouncement: true
]
)
UIAccessibility.post(
notification: .announcement,
argument: message
) In SwiftUI, you can enhance your app's accessibility by announcing interface changes to assistive technologies.
To achieve this, you can use the UIAccessibility object's post method. By setting the type to announcement and providing a String argument, you can deliver custom announcements to users who rely on assistive technologies like VoiceOver.
On iOS 17 and higher, you can also use AccessibilityNotification. In this case, create an AccessibilityNotification.Announcement with a String or AttributedString.
When providing an AttributedString, you can optionally customize the announcement behavior.
The announcement priority can be set with the accessibilitySpeechAnnouncementPriority property. Available options are: high, default and low.
For more options, check the AccessibilityAttributes struct.
swift
@State private var isLoading = false
var body: some View {
VStack {
Button("Search Appt website") {
isLoading = true
}
if isLoading {
ProgressView()
}
}
// Track state changes
.onChange(of: isLoading) { _, isLoading in
if isLoading {
announce("Search in progress")
}
}
}
// Post an announcement
func announce(_ message: String) {
if #available(iOS 17, *) {
// iOS 17+ can use AccessibilityNotification
var announcement = AttributedString(message)
announcement.accessibilitySpeechAnnouncementPriority = .high
AccessibilityNotification.Announcement(announcement).post()
} else {
// Lower iOS versions can use UIAccessibility
UIAccessibility.post(
notification: .announcement,
argument: message
)
}
} With Flutter, you can post an accessibility message by using the SemanticsService object. Use the announce method to post an accessibility announcement.
dart
SemanticsService.announce('Appt announcement', TextDirection.ltr); In React Native, you can post an accessibility message by using the AccessibilityInfo API. Use the announceForAccessibility method to post a message to assistive technologies.
jsx
AccessibilityInfo.announceForAccessibility('Appt announcement'); In MAUI, there is built-in support for posting accessibility announcements to the TalkBack or VoiceOver engine.
csharp
SemanticScreenReader.Default.Announce("Appt announcement"); csharp
public interface IA11YService
{
bool IsInVoiceOverMode { get; }
Task Speak(string? text, int pauseInMs = 0);
} csharp
private static AccessibilityManager? AccessibilityManager => Android.App.Application.Context.GetSystemService(Android.Content.Context.AccessibilityService) as AccessibilityManager;
public bool IsInVoiceOverMode => AccessibilityManager is { IsEnabled: true, IsTouchExplorationEnabled: true };
public async Task Speak(string? text, int pauseInMs = 0)
{
if (IsInVoiceOverMode && !string.IsNullOrEmpty(text))
{
if (pauseInMs > 0)
await Task.Delay(pauseInMs);
try
{
SemanticScreenReader.Announce(text);
}
catch (Exception e)
{
// Sometimes we get an exception: MauiContext must be set on parent
System.Diagnostics.Debug.WriteLine($"A11YService.Android.Speak exception: {e.Message}");
}
}
} csharp
public bool IsInVoiceOverMode => UIAccessibility.IsVoiceOverRunning;
public Task Speak(string? text, int pauseInMs = 0)
{
if (IsInVoiceOverMode && !string.IsNullOrEmpty(text))
{
if (pauseInMs > 0)
{
var dict = new NSMutableDictionary
{
{ UIView.SpeechAttributeQueueAnnouncement, new NSString("Yes") }
};
UIAccessibility.PostNotification(UIAccessibilityPostNotification.Announcement, new NSAttributedString(str: text, attributes: dict));
}
else
UIAccessibility.PostNotification(UIAccessibilityPostNotification.Announcement, new NSString(text));
}
return Task.CompletedTask;
} Xamarin Forms does not have built-in support for changing accessibility focus.
The SemanticExtensions file inside the Xamarin.CommunityToolkit contains the Announce method. It posts an accessibility announcement on the native platform.
csharp
SemanticExtensions.Announce("Appt announcement"); Use accessibility live region
On Android, a live region can be set by using the convience method setAccessibilityLiveRegion) of ViewCompat. To interrupt ingoing speech, also known as being assertive, use ACCESSIBILITY_LIVE_REGION_ASSERTIVE). To wait for ongoing speech, also known as being polite, use ACCESSIBILITY_LIVE_REGION_POLITE).
kotlin
// Interrupt ongoing speech
ViewCompat.setAccessibilityLiveRegion(view, ViewCompat.ACCESSIBILITY_LIVE_REGION_ASSERTIVE)
// Wait for ongoing speech
ViewCompat.setAccessibilityLiveRegion(view, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE) In Jetpack Compose, a live region can be set inside the semantics.semantics(kotlin.Boolean,kotlin.Function1)) block modifier. To interrupt ongoing speech, also known as being assertive, use LiveRegionMode.Assertive). To wait for ongoing speech, also known as being polite, use LiveRegionMode.Polite).
kotlin
Text(
modifier = Modifier.semantics {
liveRegion = LiveRegionMode.Polite // or LiveRegionMode.Assertive
}
) On iOS, the closest thing to live regions are elements with the updatesFrequently trait. When an element is focused, label and value changes are announced periodically.
You can replicate a live region by posting accessibility announcements. To replicate 'polite' behavior, you can set accessibilitySpeechQueueAnnouncement to false. To be 'asssertive', set the value to true.
For even more advanced behavior, you can use act on announcementDidFinishNotification events.
swift
// Periodic announcements (only on focus!)
element.accessibilityTraits = .updatesFrequently
// Replicate live region
let message = NSAttributedString(
string: "Appt live region",
attributes: [.accessibilitySpeechQueueAnnouncement: true]
)
UIAccessibility.post(notification: .announcement, argument: message) In SwiftUI, the closest thing to live regions are elements with the updatesFrequently trait. When an element is focused, label and value changes are announced periodically.
You can also replicate a live region by posting accessibility announcements. To replicate 'polite' behavior, you can set accessibilitySpeechQueueAnnouncement to false. To be 'assertive', set the value to true.
For even more advanced behavior, you can act on announcementDidFinishNotification events.
WARNING: You likely should not mix updatesFrequently trait and manual announcements. Choose one approach.
swift
@State var stockPrice = "Stock Price: $123.45"
var body: some View {
// Periodic announcements (only on focus!)
Text(stockPrice)
// Add the updatesFrequently trait here
.accessibilityAddTraits(.updatesFrequently)
.onChange(of: stockPrice) { _, newPrice in
// Replicate live region
let message = NSAttributedString(
string: newPrice,
attributes: [.accessibilitySpeechQueueAnnouncement: true]
)
UIAccessibility.post(notification: .announcement, argument: message)
}
} On Flutter, the liveRegion property can be used in Semantics to indicate a live region. By default, the live region is polite: it queues announcements.
dart
Semantics(
liveRegion: true,
child: Text('Appt live region')
); On React Native, the accessibilityLiveRegion prop can be used to indicate a live region. The value can be set to asssertive to interrupt ongoing speech to for immediate announcements on change. The polite value can be used to queue announcements. The none value can be used to disable announcements on change.
jsx
<Text accessibilityLiveRegion="assertive|polite|none">
Appt live region
</Text> IN MAUI, there is no built-in support to indicate an accessibility live region.
By using Handlers, it is possible to implement platform-specific behavior. Alternatively, you can use the accessibility Announce helper method to announce any message when you update the UI data.
csharp
SemanticScreenReader.Default.Announce("This is the announcement text."); Xamarin Forms does not have built-in support to indicate an accessibility live region. By using Effects it is possible to implement platform specific behaviour. The A11YEffect, A11YEffect for Android and A11YEffect for iOS files show how to implement an effect to replicate an accessibility live region.
xml
<controls:CustomFontLabel
effects:A11YEffect.ControlType="{OnPlatform iOS=LiveUpdate, Android=LiveUpdate}" />