Skip to content

Commit 6965d57

Browse files
mdjastrzebskifacebook-github-bot
authored andcommitted
fix(a11y): TextInput aria-label handling (#53051)
Summary: The `aria-label` prop was ignored on `TextInput` component. Which resulted in screen reader not able to read it. This PR forwards `aria-label` to `accessibilityLabel` in a manner similar to e.g. `View` and `Text` ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [GENERAL] [FIXED] - a11y: fix `aria-label` on `TextInput` Pull Request resolved: #53051 Test Plan: Run RNTester => TextInput => Accessibility section under Accessibility Inspector or screen reader. iOS fixed: https://github.com/user-attachments/assets/68c3a2ef-7dfe-479c-97fc-cbe72108c45c iOS baseline: https://github.com/user-attachments/assets/2e8372ba-10dc-47d2-b6b1-9f664000de7d Reviewed By: andrewdacenko Differential Revision: D79635413 Pulled By: rshest fbshipit-source-id: dd2f583d67c6c6c6393e02c5fe534308e1e2f921
1 parent bf2c3af commit 6965d57

5 files changed

Lines changed: 57 additions & 1 deletion

File tree

packages/react-native/Libraries/Components/TextInput/TextInput.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
618618
// so omitting onBlur and onFocus pressability handlers here.
619619
const {onBlur, onFocus, ...eventHandlers} = usePressability(config);
620620

621+
const _accessibilityLabel =
622+
props?.['aria-label'] ?? props?.accessibilityLabel;
623+
621624
let _accessibilityState;
622625
if (
623626
accessibilityState != null ||
@@ -681,6 +684,7 @@ function InternalTextInput(props: TextInputProps): React.Node {
681684
{...otherProps}
682685
{...eventHandlers}
683686
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
687+
accessibilityLabel={_accessibilityLabel}
684688
accessibilityState={_accessibilityState}
685689
accessible={accessible}
686690
submitBehavior={submitBehavior}
@@ -744,8 +748,9 @@ function InternalTextInput(props: TextInputProps): React.Node {
744748
{...otherProps}
745749
{...colorProps}
746750
{...eventHandlers}
747-
accessibilityState={_accessibilityState}
751+
accessibilityLabel={_accessibilityLabel}
748752
accessibilityLabelledBy={_accessibilityLabelledBy}
753+
accessibilityState={_accessibilityState}
749754
accessible={accessible}
750755
acceptDragAndDropTypes={props.experimental_acceptDragAndDropTypes}
751756
autoCapitalize={autoCapitalize}

packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ jest.unmock('../TextInput');
432432

433433
expect(instance.toJSON()).toMatchInlineSnapshot(`
434434
<RCTSinglelineTextInputView
435+
accessibilityLabel="label"
435436
accessibilityState={
436437
Object {
437438
"busy": true,

packages/react-native/React/Fabric/Mounting/ComponentViews/Modal/RCTModalHostViewComponentView.mm

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ static UIModalPresentationStyle presentationConfiguration(const ModalHostViewPro
9696

9797
@interface RCTModalHostViewComponentView () <RCTFabricModalHostViewControllerDelegate>
9898

99+
@property (nonatomic, weak) UIView *accessibilityFocusedView;
100+
99101
@end
100102

101103
@implementation RCTModalHostViewComponentView {
@@ -148,6 +150,7 @@ - (void)ensurePresentedOnlyIfNeeded
148150
{
149151
BOOL shouldBePresented = !_isPresented && _shouldPresent && self.window;
150152
if (shouldBePresented) {
153+
[self saveAccessibilityFocusedView];
151154
self.viewController.presentationController.delegate = self;
152155

153156
_isPresented = YES;
@@ -179,6 +182,8 @@ - (void)ensurePresentedOnlyIfNeeded
179182
if (eventEmitter) {
180183
eventEmitter->onDismiss(ModalHostViewEventEmitter::OnDismiss{});
181184
}
185+
186+
[self restoreAccessibilityFocusedView];
182187
}];
183188
}
184189
}
@@ -207,6 +212,23 @@ - (void)didMoveToSuperview
207212
[self ensurePresentedOnlyIfNeeded];
208213
}
209214

215+
- (void)saveAccessibilityFocusedView
216+
{
217+
id focusedElement = UIAccessibilityFocusedElement(nil);
218+
if (focusedElement && [focusedElement isKindOfClass:[UIView class]]) {
219+
self.accessibilityFocusedView = (UIView *)focusedElement;
220+
}
221+
}
222+
223+
- (void)restoreAccessibilityFocusedView
224+
{
225+
id viewToFocus = self.accessibilityFocusedView;
226+
if (viewToFocus) {
227+
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, viewToFocus);
228+
self.accessibilityFocusedView = nil;
229+
}
230+
}
231+
210232
#pragma mark - RCTFabricModalHostViewControllerDelegate
211233

212234
- (void)boundsDidChange:(CGRect)newBounds

packages/rn-tester/js/examples/TextInput/TextInputExample.android.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,19 @@ const examples: Array<RNTesterModuleExample> = [
451451
return <ToggleDefaultPaddingExample />;
452452
},
453453
},
454+
{
455+
title: 'Accessibility',
456+
render: function (): React.Node {
457+
return (
458+
<View>
459+
<Text>accessibilityLabel prop</Text>
460+
<ExampleTextInput accessibilityLabel="This is Accessibility Label" />
461+
<Text>aria-label prop</Text>
462+
<ExampleTextInput aria-label="This is Aria Label" />
463+
</View>
464+
);
465+
},
466+
},
454467
];
455468

456469
module.exports = ({

packages/rn-tester/js/examples/TextInput/TextInputExample.ios.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,21 @@ const textInputExamples: Array<RNTesterModuleExample> = [
10361036
);
10371037
},
10381038
},
1039+
{
1040+
title: 'Accessibility',
1041+
render: function (): React.Node {
1042+
return (
1043+
<View>
1044+
<WithLabel label="accessibilityLabel">
1045+
<ExampleTextInput accessibilityLabel="This is Accessibility Label" />
1046+
</WithLabel>
1047+
<WithLabel label="aria-label">
1048+
<ExampleTextInput aria-label="This is Aria Label" />
1049+
</WithLabel>
1050+
</View>
1051+
);
1052+
},
1053+
},
10391054
];
10401055

10411056
module.exports = ({

0 commit comments

Comments
 (0)