From 89d056266af8e6e3db91dad6217a5944027ab416 Mon Sep 17 00:00:00 2001 From: Yevhenii Date: Wed, 14 Oct 2026 14:32:49 +0300 Subject: [PATCH] Hide ScrollView and ActivityIndicator content when aria-hidden is set ScrollView and ActivityIndicator hand their props to a native component without the translation View applies, so aria-hidden reached the native side as a prop it does not read and the content stayed visible to TalkBack and VoiceOver. ScrollView now maps it to importantForAccessibility="no-hide-descendants" and accessibilityElementsHidden, as View does, and ActivityIndicator passes it to the View that wraps its indicator. RNTester's aria-hidden example gains a ScrollView and an ActivityIndicator. --- .../ActivityIndicator/ActivityIndicator.js | 2 + .../__tests__/ActivityIndicator-itest.js | 14 +++++ .../Components/ScrollView/ScrollView.js | 13 ++++- .../ScrollView/__tests__/ScrollView-itest.js | 54 +++++++++++++++++++ .../Accessibility/AccessibilityExample.js | 11 ++++ 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js index e73577e68c0b..fdb5dd589fd9 100644 --- a/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js +++ b/packages/react-native/Libraries/Components/ActivityIndicator/ActivityIndicator.js @@ -116,6 +116,7 @@ const ActivityIndicator: component( onLayout, size = 'small', style, + 'aria-hidden': ariaHidden, ...restProps }: { ref?: React.RefSetter, @@ -155,6 +156,7 @@ const ActivityIndicator: component( return ( {Platform.OS === 'android' ? ( diff --git a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js index 51636fc49de2..d0dc27457122 100644 --- a/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js +++ b/packages/react-native/Libraries/Components/ActivityIndicator/__tests__/ActivityIndicator-itest.js @@ -55,4 +55,18 @@ describe('ActivityIndicator', () => { ), ).toEqual(); }); + + it('maps aria-hidden to importantForAccessibility around its indicator', () => { + const root = Fantom.createRoot(); + Fantom.runTask(() => { + root.render(); + }); + expect( + root.getRenderedOutput({props: ['importantForAccessibility']}).toJSX(), + ).toEqual( + + + , + ); + }); }); diff --git a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js index 5c7974f2ac30..de919462bf01 100644 --- a/packages/react-native/Libraries/Components/ScrollView/ScrollView.js +++ b/packages/react-native/Libraries/Components/ScrollView/ScrollView.js @@ -1858,10 +1858,19 @@ class ScrollView extends React.Component { const baseStyle = horizontal ? styles.baseHorizontal : styles.baseVertical; - const {experimental_endDraggingSensitivityMultiplier, ...otherProps} = - this.props; + const { + experimental_endDraggingSensitivityMultiplier, + 'aria-hidden': ariaHidden, + ...otherProps + } = this.props; const props = { ...otherProps, + accessibilityElementsHidden: + ariaHidden ?? this.props.accessibilityElementsHidden, + importantForAccessibility: + ariaHidden === true + ? ('no-hide-descendants' as const) + : this.props.importantForAccessibility, alwaysBounceHorizontal, alwaysBounceVertical, style: StyleSheet.compose(baseStyle, this.props.style), diff --git a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js index 2357a7b29ebb..1a90cbfc1e2a 100644 --- a/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js +++ b/packages/react-native/Libraries/Components/ScrollView/__tests__/ScrollView-itest.js @@ -43,6 +43,60 @@ describe('', () => { }); }); + describe('aria-hidden', () => { + it('is mapped to importantForAccessibility', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect( + root.getRenderedOutput({props: ['importantForAccessibility']}).toJSX(), + ).toEqual( + + + , + ); + }); + + it('resets importantForAccessibility when set to undefined', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + Fantom.runTask(() => { + root.render(); + }); + + expect( + root.getRenderedOutput({props: ['importantForAccessibility']}).toJSX(), + ).toEqual( + + + , + ); + }); + + it('leaves an explicit importantForAccessibility in place when absent', () => { + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + expect( + root.getRenderedOutput({props: ['importantForAccessibility']}).toJSX(), + ).toEqual( + + + , + ); + }); + }); + describe('ref', () => { it('receives an instance or null', () => { const root = Fantom.createRoot(); diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js index 2ac5879c9458..b4aca3ea8e8c 100644 --- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js +++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js @@ -22,6 +22,7 @@ import * as React from 'react'; import {createRef, useEffect, useRef, useState} from 'react'; import { AccessibilityInfo, + ActivityIndicator, Alert, Button, Image, @@ -2265,6 +2266,16 @@ function AriaHiddenExample(): React.Node { source={require('../../assets/like.png')} style={styles.button} /> + + + + {ariaHidden + ? 'ScrollView content that should be hidden' + : 'ScrollView content that should be accessible'} + + + + ); }