Accessibility

Accessibility reports what the user has turned on in the system's accessibility settings: whether a screen reader is running, whether they asked for reduced motion, whether bold text is on, and their text-size multiplier.

Import
import { Accessibility } from '@ng-native/device';
Inject
inject(Accessibility)
import { Component, inject, signal } from '@angular/core';
import { Text } from '@ng-native/components';
import { Accessibility } from '@ng-native/device';

@Component({
  selector: 'app-search-results',
  imports: [Text],
  template: `<text>{{ resultsLabel() }}</text>`,
})
export class SearchResults {
  private readonly accessibility = inject(Accessibility);
  // A signal, so the text follows it: nothing re-renders for a plain field in a zoneless app.
  protected readonly resultsLabel = signal('');

  showResults(count: number): void {
    this.resultsLabel.set(`${count} results`);
    this.accessibility.announce(this.resultsLabel());
  }
}

The components already apply React Native's own accessibility defaults - roles, labels, whether a view is even exposed as an element - without any of this being injected. This service is for what those defaults cannot decide: skipping an animation because reduceMotion() is true, giving way in a layout because fontScale() is past what a single line can hold, or telling a screen reader about something that changed off screen with announce().

screenReader is whether VoiceOver or TalkBack is running. reduceMotion is the same setting @media (prefers-reduced-motion: reduce) reads, and is the better answer wherever the motion is styling rather than logic. boldText means the system font is heavier, so a hand-tuned weight may need to give way. fontScale is a multiplier of the default text size, one being the default and the largest accessibility sizes past three - it is not an event, because changing it means leaving for Settings, so it is read at startup and again when the app comes back rather than watched. announce(message) says something to a screen reader that no focus change would have said: a list that reordered, a search that returned nothing - changes a sighted user sees and a screen reader would miss.

Text itself follows a change of the system text size while the app runs, with nothing injected: watchConditions(app.engine), which the app template calls at bootstrap, measures every <text> and <text-input> again at the new size the moment the system reports it, so each one grows to fit rather than keeping the box it was first measured in. An app that does not call watchConditions can call app.engine.remeasureText() itself.

Every value here starts at its neutral setting (nothing on, a font scale of one) and is corrected once the platform answers, because React Native reports all three asynchronously - a first paint never assumes a setting is on that has not actually been confirmed.

Off a device and on the web

Off a device every setting stays at its neutral default forever and announce() does nothing, because there is no AccessibilityInfo underneath it to ask. On the web the browser's own accessibility tree and prefers-reduced-motion cover most of the same ground; Accessibility is still the one place boldText and fontScale come from on native.

Reference

Accessibilityservice@ng-native/device
import { Accessibility } from '@ng-native/device';

Signals

screenReader
Signal<boolean>

VoiceOver or TalkBack is running.

reduceMotion
Signal<boolean>

The user asked for less animation. @media (prefers-reduced-motion: reduce) reads the same setting and is the better answer wherever the motion is styling rather than logic.

boldText
Signal<boolean>

The system font is heavier, so hand-tuned weights may need to give way.

fontScale
Signal<number>

How much larger the user has asked text to be. One is the default; the largest accessibility sizes are past three, which is where a row built for one line stops working.

Methods

announce(message: string): void

Say something to a screen reader that no focus change would have said. A list that reordered, a search that returned nothing: changes a sighted user sees and a screen reader would miss.

Angular components as native iOS and Android views, built and shipped with Expo.

An alpha. MIT licensed. Sponsor its development.

An independent project, not affiliated with or endorsed by Google, the Angular team or Expo. Angular is a trademark of Google LLC.