Skip to content

Add AccessibilityInfo.getPreferences() and a longer iOS timeout while Switch Control runs - #59318

Open
camilopires wants to merge 3 commits into
react:mainfrom
camilopires:a11y/accessibility-preferences
Open

camilopires wants to merge 3 commits into
react:mainfrom
camilopires:a11y/accessibility-preferences

Conversation

@camilopires

Copy link
Copy Markdown
Contributor

Summary:

Following the discussion in react-native-community/discussions-and-proposals#1026, this adds one snapshot for accessibility settings that change how an app should draw its own controls. It replaces the one-flag-per-setting PRs #59178, #59179 and #59180.

React Native apps mostly draw their own switches and buttons with Pressable and View, so the system can't adapt them to these settings as it does UISwitch and system buttons. The app has to read the setting itself.

type AccessibilityPreferences = {
  prefersSwitchLabels: boolean;       // iOS On/Off Labels
  prefersNonColorIndicators: boolean; // iOS Differentiate Without Color
};
AccessibilityInfo.getPreferences(): Promise<AccessibilityPreferences>;
AccessibilityInfo.addEventListener('preferencesChanged', (preferences) => …);
  • Field names say what the user needs, not which platform setting it is. prefersCrossFadeTransitions already does this. A platform with no such setting reports false, so apps don't need platform checks. A future setting becomes a new field, not a new method and event.
  • preferencesChanged carries the whole new snapshot, and fires only when a field changes.
  • On Android, neither setting exists. getPreferences() resolves both fields to false without a native call, and preferencesChanged doesn't subscribe, like the other iOS-only events.

Switch Control uses the existing getRecommendedTimeoutMillis(originalTimeout) rather than a new flag or method. Until now it read Android's "Time to take action" setting and returned originalTimeout unchanged on iOS. On iOS it now returns three times originalTimeout while Switch Control is running. Moving between elements one switch press at a time is slow, so a 4-second toast is often gone before you reach it. Apple publishes no recommended figure, so 3× is a judgement call, and I'm happy to change it. This also replaces the separate getRecommendedTimeout I suggested on #1026, since the method already exists.

Scope: Show Borders (#59177) has already landed as isShowBordersEnabled(), and the Android contrast level (#59181) is in review, so neither is folded into the snapshot here. #59176 (Bold Text on Android) is separate. It also edits AccessibilityInfo.js, so whichever lands second needs a small rebase.

Changelog:

[IOS] [ADDED] - AccessibilityInfo.getPreferences() and preferencesChanged (On/Off Labels and Differentiate Without Color)
[IOS] [CHANGED] - AccessibilityInfo.getRecommendedTimeoutMillis() returns a longer timeout while Switch Control is running

Test Plan:

  • yarn jest packages/react-native/Libraries/Components/AccessibilityInfo: 49 tests pass. They cover getPreferences on both platforms (including an older iOS binary without the native method), the iOS timeout with and without the native method, and the event wiring.
  • yarn flow-check: 0 errors
  • yarn lint: passes
  • yarn build-types --validate: API snapshot up to date
  • yarn test-generated-typescript and yarn test-typescript-legacy: pass
  • yarn format-check-cpp, and Prettier on the changed JS and TS: pass
  • New RCTAccessibilityManagerTests in RNTesterUnitTests: 5 tests pass. They replace the system setting readers, post the real UIKit notifications, and call getCurrentPreferences and getRecommendedTimeoutMillis. They check the startup snapshot, that each setting's change sends the whole snapshot, that a notification without a change sends nothing, and the timeout with Switch Control on and off. Removing the On/Off Labels observer makes its test fail.
  • RNTester builds for the iOS simulator with CocoaPods, using Xcode 27.1.

On an iPhone 18 Pro simulator running iOS 27.0, I switched each setting in the Settings app with a throwaway XCUITest (not part of this PR). Then I read RNTester's Accessibility → "display options" rows. RNTester stayed running, so the "Last preferencesChanged" row could only update through the event.

Step Last preferencesChanged getPreferences() afterwards
Before none switch labels off, non-color indicators off
On/Off Labels on switch labels on, non-color indicators off
On/Off Labels off switch labels off, non-color indicators off switch labels off, non-color indicators off
Differentiate Without Colour on switch labels off, non-color indicators on
Differentiate Without Colour off switch labels off, non-color indicators off switch labels off, non-color indicators off

The recommended-timeout row read "4000 ms" with Switch Control off. The row reads the value again whenever RNTester becomes active, because Switch Control has no change event.

Android: RNTester on an API 36 emulator shows "Preferences: switch labels off, non-color indicators off.", "Last preferencesChanged: none." and "Recommended timeout for 4000 ms: 4000 ms.", with no errors.

Not verified: the timeout with Switch Control running. The iOS simulator has no Switch Control, so it needs a physical device. The policy itself is unit-tested. I didn't run a SwiftPM build either, because this PR doesn't change linking or Package.swift.

camilopires and others added 3 commits October 20, 2026 20:14
Add a preferences snapshot for accessibility settings that change how an
app should draw its own controls, named by what the user needs rather
than by each platform's setting: prefersSwitchLabels (iOS On/Off Labels)
and prefersNonColorIndicators (iOS Differentiate Without Color). Android
has neither setting, so it resolves both to false. preferencesChanged
carries the whole new snapshot.

getRecommendedTimeoutMillis now asks the iOS module too, so iOS can
return a longer timeout while Switch Control is running.

Adds Jest tests, type tests, the API snapshot and an RNTester row. The
iOS native side follows in the next commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RCTAccessibilityManager builds the getPreferences() snapshot from On/Off
Labels (UIAccessibilityIsOnOffSwitchLabelsEnabled) and Differentiate
Without Color (UIAccessibilityShouldDifferentiateWithoutColor), observes
both settings' notifications, and sends preferencesChanged with the
whole snapshot only when it changes.

getRecommendedTimeoutMillis returns three times the original timeout
while Switch Control is running, because moving between elements one
switch press at a time is slow.

Adds RCTAccessibilityManagerTests for the timeout and for sending the
event only on a change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RCTAccessibilityManager now reads the On/Off Labels, Differentiate
Without Color and Switch Control settings through readers that tests can
replace (RCTAccessibilityManagerSetSystemReaders, in +Internal.h). The
tests post the real UIKit notifications and call getCurrentPreferences
and getRecommendedTimeoutMillis, so they cover the observers, the
deduplication and the native callbacks, not just a helper. This replaces
the two earlier test hooks.

RNTester's preferences row also reads the snapshot and the recommended
timeout again when the app becomes active, because Switch Control has
no change event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Oct 21, 2026
@github-actions

Copy link
Copy Markdown

Warning

JavaScript API change detected

This PR commits an update to ReactNativeApi.d.ts, indicating a change to React Native's public JavaScript API.

  • Please include a clear changelog message.
  • This change will be subject to additional review.

This change was flagged as: POTENTIALLY_BREAKING

@cipolleschi

Copy link
Copy Markdown
Contributor

there are several conflicts in this PR. Can you rebase on top of main please?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants