From 4530d6071896bbe041a811ba22a52ce4f66909af Mon Sep 17 00:00:00 2001 From: Carmen Krol Date: Mon, 15 May 2023 07:49:17 -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. Differential Revision: https://www.internalfb.com/diff/D45797554?entry_point=27 fbshipit-source-id: 6354a7687d2c70c2736aa8e426ba043f58f1f502 --- .../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 60e0697071d8ee..e946629f67c77a 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -710,21 +710,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;