skip to content

Reader Detection & Announcements

AccessibilityInfo reports whether a screen reader, reduce motion or bold text is on, and can announce a change or move focus. Interviewers probe adapting behaviour without forking the whole UI.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

4

In React Native, how do announceForAccessibility and accessibilityLiveRegion differ for telling screen-reader users a money transfer completed?

level: middleimportance: must knowfreq 48%

answer

  1. speak it, or mark the region
  2. announceForAccessibility works on both
  3. accessibilityLiveRegion is Android-only
  4. aria-live alias: off, polite, assertive
  5. iOS options: queue and priority

basics

~20 s

AccessibilityInfo.announceForAccessibility speaks a string on both iOS and Android at the moment you call it; accessibilityLiveRegion makes TalkBack read a view when its content changes but does nothing on iOS, so VoiceOver users still need the imperative call.

solid answer

~40 s

When the transfer succeeds, focus stays on the Send button, so a screen-reader user hears nothing unless the app says something. `AccessibilityInfo.announceForAccessibility('Transfer of 50 euros to Ana completed')` makes VoiceOver or TalkBack speak the string on both platforms. The declarative alternative, `accessibilityLiveRegion="polite"` on the status `Text` (or the `aria-live` alias on a `View`), makes TalkBack read the view when its content changes, but it is Android-only, so iOS still needs the imperative call, and using both on Android can speak the message twice. On iOS, `announceForAccessibilityWithOptions` adds `queue`, to wait behind current speech, and `priority` (`low`, `default`, `high`, since 0.84). I announce the outcome in a full sentence, failures included, and keep the same text visible on screen.

code

tsx · 29 lines
tsx
import {useEffect} from 'react';
import {AccessibilityInfo, Platform, Text, View} from 'react-native';

type TransferStatusProps = {
  status: 'idle' | 'sending' | 'done' | 'failed';
  amount: string;
  payee: string;
};

export function TransferStatus({status, amount, payee}: TransferStatusProps) {
  const message =
    status === 'done'
      ? `Transfer of ${amount} to ${payee} completed.`
      : status === 'failed'
        ? 'Transfer failed. Your balance was not changed.'
        : '';

  useEffect(() => {
    if (message !== '' && Platform.OS === 'ios') {
      AccessibilityInfo.announceForAccessibility(message);
    }
  }, [message]);

  return (
    <View>
      <Text accessibilityLiveRegion="polite">{message}</Text>
    </View>
  );
}

go deeper

for a junior

Recall that a confirmation that appears without taking focus is silent for screen-reader users, and that announceForAccessibility speaks a string on both platforms.

for a middle

Explain the two mechanisms, why accessibilityLiveRegion only helps TalkBack, and how to avoid a double announcement on Android when you also call announceForAccessibility.

for a senior

Show how you handle an announcement that collides with a navigation, the iOS queue and priority options, and failure wording, and how you check it with VoiceOver and TalkBack on devices.

for a principal

Weigh a shared status component that hides the per-platform announcement logic against ad-hoc calls scattered through feature screens, and who owns its wording.

## The situation A user taps **Send** on a transfer screen. The request succeeds, a line reading "Transfer completed" appears under the button, and focus stays on the button. A sighted user sees the confirmation; a **VoiceOver** (iOS) or **TalkBack** (Android) user hears nothing, because a screen reader speaks what it is focused on and the new text never received focus. React Native offers two ways to make the confirmation audible, and they are not equivalent across platforms. ## Option 1: `AccessibilityInfo.announceForAccessibility` `announceForAccessibility(announcement: string)` asks the running screen reader to speak a string. It is **imperative**, meaning you call it at the moment the outcome is known, and it works on **both iOS and Android**. - The text should be a complete sentence that stands alone: "Transfer of 50 euros to Ana completed", not "Done". - `announceForAccessibilityWithOptions(announcement, options)` adds options that only iOS honours: - `queue: true` waits for current speech to finish instead of interrupting it; - `priority` (`'low'`, `'default'`, `'high'`, added in React Native 0.84) sets interruption: high interrupts ongoing speech and cannot itself be interrupted, default interrupts but can be interrupted, low does not interrupt and can be interrupted. - On Android the options object is ignored and the call behaves like `announceForAccessibility`. - On iOS the `announcementFinished` event, subscribed with `addEventListener`, reports `{announcement, success}`, so you can tell whether it was actually spoken. ## Option 2: a live region A **live region** is a view the screen reader watches: when its content changes, the change is read out without moving focus. In React Native it is the `accessibilityLiveRegion` prop, or its `aria-live` alias on `View`: | `accessibilityLiveRegion` | `aria-live` | Effect on TalkBack | |---|---|---| | `'none'` | `'off'` (the default) | changes are not announced | | `'polite'` | `'polite'` | changes are announced | | `'assertive'` | `'assertive'` | ongoing speech is interrupted to announce the change | The catch: **live regions are Android-only in React Native**. The prop is documented for Android and iOS ignores it, so a status line marked `polite` is read by TalkBack and stays silent under VoiceOver. ## Choosing and combining | Need | iOS | Android | |---|---|---| | Speak a one-off outcome | `announceForAccessibility` | `announceForAccessibility` or a live region | | Re-read a status each time it changes | `announceForAccessibility` on each change | live region | | Wait behind current speech | `queue: true` | not available | A common cross-platform pattern is a live region on the visible status `Text` for Android plus an iOS-only `announceForAccessibility` call. Firing **both** mechanisms on Android risks the user hearing the message twice, so gate the imperative call by `Platform.OS` or pick one mechanism per platform. ## Timing and wording 1. Announce **after** the outcome is certain, on the success response, not when the button is pressed. 2. Announce failures too, in words that say what happened and what to do: "Transfer failed. Your balance was not changed. Try again." 3. Watch for collisions. If the app navigates to a receipt screen at the same moment, the screen reader starts reading the new screen and can cut the announcement short. Announce after the transition, raise the iOS `priority`, or move focus to the receipt's heading instead. 4. Keep the announced text visible on screen as well, so people with partial sight get the same information. 5. Do not announce every intermediate state such as validating and sending; one clear outcome per action is enough. ## What this is not Announcements supplement correct labels, roles and states; they do not replace them. A Send button that is read as an unlabeled "button" is not fixed by announcing what it did afterwards, and a confirmation announced but never shown on screen leaves sighted users guessing.

  • How can you tell on iOS whether an announcement was actually spoken?
    Subscribe with `AccessibilityInfo.addEventListener('announcementFinished', handler)`. The handler receives `{announcement, success}`, where `success` says whether VoiceOver finished speaking it. The event is iOS-only; on Android the subscription is inert, so do not build retry logic that depends on it there.
  • What goes wrong if the app navigates to a receipt screen in the same tick as the announcement?
    The screen reader starts reading the newly presented screen, and that speech can cut the announcement short. Announce once the transition has finished, raise the iOS `priority` to `high` so it cannot be interrupted, or put the confirmation on the receipt screen and move focus to its heading instead of announcing.
  • When is accessibilityLiveRegion="assertive" the better value on the transfer screen?
    For a message the user must hear now, such as a failed transfer, because `assertive` interrupts current TalkBack speech. A success confirmation is usually `polite`. Overusing `assertive` cuts off whatever the user was listening to, and it still has no effect on iOS.

saying these in an interview costs you the question

  • Setting accessibilityLiveRegion on the status Text makes VoiceOver read it on iOS.
  • announceForAccessibility only works on iOS; Android needs a native module for announcements.
  • Announce a short word such as Done, because the context is obvious.
  • A live region plus announceForAccessibility on Android is harmless redundancy.
  • The queue option of announceForAccessibilityWithOptions also queues speech on Android.
open as a page

In React Native, how do you check whether a screen reader or reduce motion is on, and keep that value current while the app runs?

level: juniorimportance: should knowfreq 38%

basics

~10 s

Call AccessibilityInfo.isScreenReaderEnabled() or isReduceMotionEnabled(), which return promises, for the current value, and subscribe with addEventListener to screenReaderChanged or reduceMotionChanged for later changes, removing the subscription on unmount.

open as a page

In React Native's AccessibilityInfo, what happens when code queries or subscribes to a setting the current platform does not support?

level: middleimportance: should knowfreq 20%

basics

~10 s

Mostly nothing visible: an iOS-only query such as isBoldTextEnabled resolves to false on Android, an unmapped event returns an inert subscription, and isAccessibilityServiceEnabled rejects on iOS, so false often means unsupported rather than off.

open as a page

In React Native, how do you move VoiceOver or TalkBack focus to a transfer result heading, and why might focus not land there?

level: seniorimportance: nice to knowfreq 22%

basics

~20 s

Call AccessibilityInfo.sendAccessibilityEvent with the heading's host instance from a ref and the 'focus' event type, after the heading has rendered; focus fails to land when the ref is null, the target is not accessible, or the platform moves focus afterwards.

open as a page