diff --git a/Libraries/Components/View/View.js b/Libraries/Components/View/View.js
index 129f50d36e2d95..8ef1f814a312fb 100644
--- a/Libraries/Components/View/View.js
+++ b/Libraries/Components/View/View.js
@@ -12,6 +12,7 @@ import type {ViewProps} from './ViewPropTypes';
import flattenStyle from '../../StyleSheet/flattenStyle';
import TextAncestor from '../../Text/TextAncestor';
+import {getAccessibilityRoleFromRole} from '../../Utilities/AcessibilityMapping';
import ViewNativeComponent from './ViewNativeComponent';
import * as React from 'react';
@@ -80,74 +81,6 @@ const View: React.AbstractComponent<
text: ariaValueText ?? accessibilityValue?.text,
};
- // Map role values to AccessibilityRole values
- const roleToAccessibilityRoleMapping = {
- alert: 'alert',
- alertdialog: undefined,
- application: undefined,
- article: undefined,
- banner: undefined,
- button: 'button',
- cell: undefined,
- checkbox: 'checkbox',
- columnheader: undefined,
- combobox: 'combobox',
- complementary: undefined,
- contentinfo: undefined,
- definition: undefined,
- dialog: undefined,
- directory: undefined,
- document: undefined,
- feed: undefined,
- figure: undefined,
- form: undefined,
- grid: 'grid',
- group: undefined,
- heading: 'header',
- img: 'image',
- link: 'link',
- list: 'list',
- listitem: undefined,
- log: undefined,
- main: undefined,
- marquee: undefined,
- math: undefined,
- menu: 'menu',
- menubar: 'menubar',
- menuitem: 'menuitem',
- meter: undefined,
- navigation: undefined,
- none: 'none',
- note: undefined,
- presentation: 'none',
- progressbar: 'progressbar',
- radio: 'radio',
- radiogroup: 'radiogroup',
- region: undefined,
- row: undefined,
- rowgroup: undefined,
- rowheader: undefined,
- scrollbar: 'scrollbar',
- searchbox: 'search',
- separator: undefined,
- slider: 'adjustable',
- spinbutton: 'spinbutton',
- status: undefined,
- summary: 'summary',
- switch: 'switch',
- tab: 'tab',
- table: undefined,
- tablist: 'tablist',
- tabpanel: undefined,
- term: undefined,
- timer: 'timer',
- toolbar: 'toolbar',
- tooltip: undefined,
- tree: undefined,
- treegrid: undefined,
- treeitem: undefined,
- };
-
const flattenedStyle = flattenStyle(style);
const newPointerEvents = flattenedStyle?.pointerEvents || pointerEvents;
@@ -162,7 +95,7 @@ const View: React.AbstractComponent<
focusable={tabIndex !== undefined ? !tabIndex : focusable}
accessibilityState={_accessibilityState}
accessibilityRole={
- role ? roleToAccessibilityRoleMapping[role] : accessibilityRole
+ role ? getAccessibilityRoleFromRole(role) : accessibilityRole
}
accessibilityElementsHidden={
ariaHidden ?? accessibilityElementsHidden
diff --git a/Libraries/Components/View/ViewAccessibility.d.ts b/Libraries/Components/View/ViewAccessibility.d.ts
index a9ff8baa374686..8f530a78c19848 100644
--- a/Libraries/Components/View/ViewAccessibility.d.ts
+++ b/Libraries/Components/View/ViewAccessibility.d.ts
@@ -101,6 +101,11 @@ export interface AccessibilityProps
'aria-live'?: ('polite' | 'assertive' | 'off') | undefined;
'aria-modal'?: boolean | undefined;
+
+ /**
+ * Indicates to accessibility services to treat UI component like a specific role.
+ */
+ role?: Role;
}
export type AccessibilityActionInfo = Readonly<{
@@ -286,3 +291,69 @@ export interface AccessibilityPropsIOS {
*/
accessibilityIgnoresInvertColors?: boolean | undefined;
}
+
+export type Role =
+ | 'alert'
+ | 'alertdialog'
+ | 'application'
+ | 'article'
+ | 'banner'
+ | 'button'
+ | 'cell'
+ | 'checkbox'
+ | 'columnheader'
+ | 'combobox'
+ | 'complementary'
+ | 'contentinfo'
+ | 'definition'
+ | 'dialog'
+ | 'directory'
+ | 'document'
+ | 'feed'
+ | 'figure'
+ | 'form'
+ | 'grid'
+ | 'group'
+ | 'heading'
+ | 'img'
+ | 'link'
+ | 'list'
+ | 'listitem'
+ | 'log'
+ | 'main'
+ | 'marquee'
+ | 'math'
+ | 'menu'
+ | 'menubar'
+ | 'menuitem'
+ | 'meter'
+ | 'navigation'
+ | 'none'
+ | 'note'
+ | 'presentation'
+ | 'progressbar'
+ | 'radio'
+ | 'radiogroup'
+ | 'region'
+ | 'row'
+ | 'rowgroup'
+ | 'rowheader'
+ | 'scrollbar'
+ | 'searchbox'
+ | 'separator'
+ | 'slider'
+ | 'spinbutton'
+ | 'status'
+ | 'summary'
+ | 'switch'
+ | 'tab'
+ | 'table'
+ | 'tablist'
+ | 'tabpanel'
+ | 'term'
+ | 'timer'
+ | 'toolbar'
+ | 'tooltip'
+ | 'tree'
+ | 'treegrid'
+ | 'treeitem';
diff --git a/Libraries/Text/Text.js b/Libraries/Text/Text.js
index ac427bf8c736ae..2470a3c2627bc6 100644
--- a/Libraries/Text/Text.js
+++ b/Libraries/Text/Text.js
@@ -15,6 +15,7 @@ import usePressability from '../Pressability/usePressability';
import flattenStyle from '../StyleSheet/flattenStyle';
import processColor from '../StyleSheet/processColor';
import StyleSheet from '../StyleSheet/StyleSheet';
+import {getAccessibilityRoleFromRole} from '../Utilities/AcessibilityMapping';
import Platform from '../Utilities/Platform';
import TextAncestor from './TextAncestor';
import {NativeText, NativeVirtualText} from './TextNativeComponent';
@@ -34,6 +35,7 @@ const Text: React.AbstractComponent<
const {
accessible,
accessibilityLabel,
+ accessibilityRole,
allowFontScaling,
'aria-busy': ariaBusy,
'aria-checked': ariaChecked,
@@ -55,6 +57,7 @@ const Text: React.AbstractComponent<
onResponderTerminationRequest,
onStartShouldSetResponder,
pressRetentionOffset,
+ role,
suppressHighlighting,
...restProps
} = props;
@@ -223,6 +226,9 @@ const Text: React.AbstractComponent<
accessibilityState={_accessibilityState}
{...eventHandlersForText}
accessibilityLabel={ariaLabel ?? accessibilityLabel}
+ accessibilityRole={
+ role ? getAccessibilityRoleFromRole(role) : accessibilityRole
+ }
isHighlighted={isHighlighted}
isPressable={isPressable}
selectable={_selectable}
@@ -246,6 +252,9 @@ const Text: React.AbstractComponent<
}
accessibilityLabel={ariaLabel ?? accessibilityLabel}
accessibilityState={nativeTextAccessibilityState}
+ accessibilityRole={
+ role ? getAccessibilityRoleFromRole(role) : accessibilityRole
+ }
allowFontScaling={allowFontScaling !== false}
ellipsizeMode={ellipsizeMode ?? 'tail'}
isHighlighted={isHighlighted}
diff --git a/Libraries/Text/TextProps.js b/Libraries/Text/TextProps.js
index 3a927ae48681e4..4ef9e7cec3ce39 100644
--- a/Libraries/Text/TextProps.js
+++ b/Libraries/Text/TextProps.js
@@ -15,6 +15,7 @@ import type {
AccessibilityActionInfo,
AccessibilityRole,
AccessibilityState,
+ Role,
} from '../Components/View/ViewAccessibility';
import type {TextStyleProp} from '../StyleSheet/StyleSheet';
import type {
@@ -176,6 +177,11 @@ export type TextProps = $ReadOnly<{|
*/
pressRetentionOffset?: ?PressRetentionOffset,
+ /**
+ * Indicates to accessibility services to treat UI component like a specific role.
+ */
+ role?: ?Role,
+
/**
* Lets the user select text.
*
diff --git a/Libraries/Utilities/AcessibilityMapping.js b/Libraries/Utilities/AcessibilityMapping.js
new file mode 100644
index 00000000000000..8db68118821dd8
--- /dev/null
+++ b/Libraries/Utilities/AcessibilityMapping.js
@@ -0,0 +1,152 @@
+/**
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *
+ * @flow strict-local
+ * @format
+ */
+
+'use strict';
+
+import type {
+ AccessibilityRole,
+ Role,
+} from '../Components/View/ViewAccessibility';
+
+// Map role values to AccessibilityRole values
+export function getAccessibilityRoleFromRole(role: Role): ?AccessibilityRole {
+ switch (role) {
+ case 'alert':
+ return 'alert';
+ case 'alertdialog':
+ return undefined;
+ case 'application':
+ return undefined;
+ case 'article':
+ return undefined;
+ case 'banner':
+ return undefined;
+ case 'button':
+ return 'button';
+ case 'cell':
+ return undefined;
+ case 'checkbox':
+ return 'checkbox';
+ case 'columnheader':
+ return undefined;
+ case 'combobox':
+ return 'combobox';
+ case 'complementary':
+ return undefined;
+ case 'contentinfo':
+ return undefined;
+ case 'definition':
+ return undefined;
+ case 'dialog':
+ return undefined;
+ case 'directory':
+ return undefined;
+ case 'document':
+ return undefined;
+ case 'feed':
+ return undefined;
+ case 'figure':
+ return undefined;
+ case 'form':
+ return undefined;
+ case 'grid':
+ return 'grid';
+ case 'group':
+ return undefined;
+ case 'heading':
+ return 'header';
+ case 'img':
+ return 'image';
+ case 'link':
+ return 'link';
+ case 'list':
+ return 'list';
+ case 'listitem':
+ return undefined;
+ case 'log':
+ return undefined;
+ case 'main':
+ return undefined;
+ case 'marquee':
+ return undefined;
+ case 'math':
+ return undefined;
+ case 'menu':
+ return 'menu';
+ case 'menubar':
+ return 'menubar';
+ case 'menuitem':
+ return 'menuitem';
+ case 'meter':
+ return undefined;
+ case 'navigation':
+ return undefined;
+ case 'none':
+ return 'none';
+ case 'note':
+ return undefined;
+ case 'presentation':
+ return 'none';
+ case 'progressbar':
+ return 'progressbar';
+ case 'radio':
+ return 'radio';
+ case 'radiogroup':
+ return 'radiogroup';
+ case 'region':
+ return undefined;
+ case 'row':
+ return undefined;
+ case 'rowgroup':
+ return undefined;
+ case 'rowheader':
+ return undefined;
+ case 'scrollbar':
+ return 'scrollbar';
+ case 'searchbox':
+ return 'search';
+ case 'separator':
+ return undefined;
+ case 'slider':
+ return 'adjustable';
+ case 'spinbutton':
+ return 'spinbutton';
+ case 'status':
+ return undefined;
+ case 'summary':
+ return 'summary';
+ case 'switch':
+ return 'switch';
+ case 'tab':
+ return 'tab';
+ case 'table':
+ return undefined;
+ case 'tablist':
+ return 'tablist';
+ case 'tabpanel':
+ return undefined;
+ case 'term':
+ return undefined;
+ case 'timer':
+ return 'timer';
+ case 'toolbar':
+ return 'toolbar';
+ case 'tooltip':
+ return undefined;
+ case 'tree':
+ return undefined;
+ case 'treegrid':
+ return undefined;
+ case 'treeitem':
+ return undefined;
+ }
+
+ return undefined;
+}
diff --git a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js
index 9efdbfdb2da011..c552f75699656a 100644
--- a/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js
+++ b/packages/rn-tester/js/examples/Accessibility/AccessibilityExample.js
@@ -157,6 +157,10 @@ class AccessibilityExample extends React.Component<{}> {
This is a title.
+
+ This is a title.
+
+
Alert.alert('Link has been clicked!')}