From 12f4a19306b9d4f77c1b5493159de99f897b1862 Mon Sep 17 00:00:00 2001 From: Carmen Krol Date: Mon, 15 May 2023 10:16:22 -0700 Subject: [PATCH] Announce checkbox and radio button roles on VoiceOver (#37414) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/37414 Previously, when focusing on a checkbox or radio button in React Native Fabric with VoiceOver, it wasn't announcing the role, e.g. "checkbox". Instead it would just say [label][state], e.g. "Automatically check for updates, unchecked". This is an extremely confusing experience for screen reader users because they don't know what kind of element they are focusing, including how to interact with it. "checkbox" and "radio button" aren't recognized as [Apple iOS traits](https://developer.apple.com/documentation/uikit/uiaccessibilitytraits?language=objc), but we'd like to have consistency with the mobile safari experience. Reviewed By: cipolleschi Differential Revision: D45797554 fbshipit-source-id: 418e73342aaa8d0986e330bfd54078be27d3491f --- .../View/RCTViewComponentView.mm | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index e2239c99531559..ecfe8bc408fd95 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -721,21 +721,40 @@ - (NSString *)accessibilityValue } } + NSMutableArray *valueComponents = [NSMutableArray new]; + NSString *roleString = [NSString stringWithUTF8String:props.accessibilityRole.c_str()]; + + // In iOS, checkbox and radio buttons aren't recognized as traits. However, + // because our apps use checkbox and radio buttons often, we should announce + // these to screenreader users. (They should already be familiar with them + // from using web). + if ([roleString isEqualToString:@"checkbox"]) { + [valueComponents addObject:@"checkbox"]; + } + + if ([roleString isEqualToString:@"radio"]) { + [valueComponents addObject:@"radio button"]; + } + // Handle states which haven't already been handled. if (props.accessibilityState.checked == AccessibilityState::Checked) { - return @"checked"; + [valueComponents addObject:@"checked"]; } if (props.accessibilityState.checked == AccessibilityState::Unchecked) { - return @"unchecked"; + [valueComponents addObject:@"unchecked"]; } if (props.accessibilityState.checked == AccessibilityState::Mixed) { - return @"mixed"; + [valueComponents addObject:@"mixed"]; } if (props.accessibilityState.expanded) { - return @"expanded"; + [valueComponents addObject:@"expanded"]; } if (props.accessibilityState.busy) { - return @"busy"; + [valueComponents addObject:@"busy"]; + } + + if (valueComponents.count > 0) { + return [valueComponents componentsJoinedByString:@", "]; } return nil;