From 6f14aa09f9fa19b1371ef1d89303430b584760df Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 31 Jan 2026 09:40:09 +0300 Subject: [PATCH 1/6] fix: Screen Reader: Split Expense: Elements of each row are grouped, cannot focus/activate separately --- src/components/SelectionList/ListItem/BaseListItem.tsx | 2 ++ src/components/SelectionList/ListItem/SplitListItem.tsx | 1 + src/components/SelectionList/ListItem/types.ts | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index a41a9b93c161b..ef788a6795ec1 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -47,6 +47,7 @@ function BaseListItem({ shouldDisableHoverStyle, shouldStopMouseLeavePropagation = true, shouldShowRightCaret = false, + accessible, }: BaseListItemProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -137,6 +138,7 @@ function BaseListItem({ tabIndex={item.tabIndex} wrapperStyle={pressableWrapperStyle} testID={testID} + accessible={accessible} > ({ keyForList={item.keyForList} onFocus={onFocus} pendingAction={item.pendingAction} + accessible={!splitItem.isEditable} > diff --git a/src/components/SelectionList/ListItem/types.ts b/src/components/SelectionList/ListItem/types.ts index 8301690764467..da4052bae2a46 100644 --- a/src/components/SelectionList/ListItem/types.ts +++ b/src/components/SelectionList/ListItem/types.ts @@ -314,6 +314,12 @@ type BaseListItemProps = CommonListItemProps & { /** Whether to call stopPropagation on the mouseleave event in BaseListItem */ shouldStopMouseLeavePropagation?: boolean; + + /** + * Whether the pressable should be accessible as a single element. + * When false, allows child elements (like TextInput) to be independently focusable by screen readers. + */ + accessible?: boolean; }; type SplitListItemType = ListItem & From e89a037b5e7b6fe015d66564a7e8878901a17cda Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 31 Jan 2026 09:50:53 +0300 Subject: [PATCH 2/6] fix: add missing sentryLabel prop to BaseListItem PressableWithFeedback --- src/components/SelectionList/ListItem/BaseListItem.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index ef788a6795ec1..c24f3d88b88b2 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -139,6 +139,7 @@ function BaseListItem({ wrapperStyle={pressableWrapperStyle} testID={testID} accessible={accessible} + sentryLabel={`BaseListItem-${keyForList}`} > Date: Sat, 31 Jan 2026 10:38:16 +0300 Subject: [PATCH 3/6] fix: make split expense row elements accessible and consistent across iOS/Android --- .../SelectionList/ListItem/SplitListItem.tsx | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/ListItem/SplitListItem.tsx b/src/components/SelectionList/ListItem/SplitListItem.tsx index aca0fff399fe0..120ac72da9fee 100644 --- a/src/components/SelectionList/ListItem/SplitListItem.tsx +++ b/src/components/SelectionList/ListItem/SplitListItem.tsx @@ -1,11 +1,13 @@ import React, {useCallback, useEffect, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import Icon from '@components/Icon'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import type {BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -34,6 +36,7 @@ function SplitListItem({ const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'Folder', 'Tag'] as const); const theme = useTheme(); const styles = useThemeStyles(); + const {translate} = useLocalize(); const {didScreenTransitionEnd} = useScreenWrapperTransitionStatus(); const splitItem = item as unknown as SplitListItemType; @@ -83,6 +86,16 @@ function SplitListItem({ const isPercentageMode = splitItem.mode === CONST.TAB.SPLIT.PERCENTAGE; + // Build accessibility label for the grouped text content (date, merchant, category, tags) + const textContentAccessibilityLabel = [ + splitItem.headerText, + splitItem.merchant, + splitItem.category ? getDecodedCategoryName(splitItem.category) : undefined, + splitItem.tags?.at(0) ? getCommaSeparatedTagNameWithSanitizedColons(splitItem.tags.at(0) ?? '') : undefined, + ] + .filter(Boolean) + .join(', '); + return ( ({ accessible={!splitItem.isEditable} > - + ({ {!splitItem.isEditable ? null : ( - + onSelectRow(item)} + accessibilityLabel={translate('common.edit')} + role="button" + style={styles.pointerEventsAuto} + sentryLabel="SplitListItem-EditButton" + > - + )} From bc80b302d86a2d53aa034a90c682629cd81b424e Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 31 Jan 2026 10:52:22 +0300 Subject: [PATCH 4/6] fix: make split expense text content keyboard-focusable on web --- src/components/SelectionList/ListItem/SplitListItem.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/SelectionList/ListItem/SplitListItem.tsx b/src/components/SelectionList/ListItem/SplitListItem.tsx index 120ac72da9fee..5758d9d0fee2e 100644 --- a/src/components/SelectionList/ListItem/SplitListItem.tsx +++ b/src/components/SelectionList/ListItem/SplitListItem.tsx @@ -120,6 +120,8 @@ function SplitListItem({ style={[styles.flex1]} accessible={splitItem.isEditable} accessibilityLabel={textContentAccessibilityLabel} + tabIndex={splitItem.isEditable ? 0 : undefined} + role={splitItem.isEditable ? 'summary' : undefined} > From 4c8dc044b3e720de2ee3470d9ed05161f13df138 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 31 Jan 2026 10:59:23 +0300 Subject: [PATCH 5/6] fix: use CONST.ROLE.SUMMARY for split expense text content role --- src/components/SelectionList/ListItem/SplitListItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SelectionList/ListItem/SplitListItem.tsx b/src/components/SelectionList/ListItem/SplitListItem.tsx index 5758d9d0fee2e..309c36a08c76e 100644 --- a/src/components/SelectionList/ListItem/SplitListItem.tsx +++ b/src/components/SelectionList/ListItem/SplitListItem.tsx @@ -121,7 +121,7 @@ function SplitListItem({ accessible={splitItem.isEditable} accessibilityLabel={textContentAccessibilityLabel} tabIndex={splitItem.isEditable ? 0 : undefined} - role={splitItem.isEditable ? 'summary' : undefined} + role={splitItem.isEditable ? CONST.ROLE.SUMMARY : undefined} > From 9504a4bf314beb19b59db64e1db70b1f4ca17e7f Mon Sep 17 00:00:00 2001 From: TaduJR Date: Sat, 31 Jan 2026 11:33:12 +0300 Subject: [PATCH 6/6] fix: hide child elements from accessibility tree to fix grouping on Android mWeb --- .../SelectionList/ListItem/BaseListItem.tsx | 4 ++-- .../SelectionList/ListItem/SplitListItem.tsx | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index c24f3d88b88b2..1810738698b0a 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -114,7 +114,6 @@ function BaseListItem({ disabled={isDisabled && !item.isSelected} interactive={item.isInteractive} accessibilityLabel={item.accessibilityLabel ?? [item.text, item.text !== item.alternateText ? item.alternateText : undefined].filter(Boolean).join(', ')} - role={getButtonRole(true)} isNested hoverDimmingValue={1} pressDimmingValue={item.isInteractive === false ? 1 : variables.pressDimValue} @@ -135,10 +134,11 @@ function BaseListItem({ ]} onFocus={onFocus} onMouseLeave={handleMouseLeave} - tabIndex={item.tabIndex} + tabIndex={accessible === false ? -1 : item.tabIndex} wrapperStyle={pressableWrapperStyle} testID={testID} accessible={accessible} + role={accessible === false ? CONST.ROLE.PRESENTATION : getButtonRole(true)} sentryLabel={`BaseListItem-${keyForList}`} > ({ style={[styles.flex1]} accessible={splitItem.isEditable} accessibilityLabel={textContentAccessibilityLabel} + aria-label={splitItem.isEditable ? textContentAccessibilityLabel : undefined} tabIndex={splitItem.isEditable ? 0 : undefined} role={splitItem.isEditable ? CONST.ROLE.SUMMARY : undefined} > - + ({ {isBottomVisible && ( - + {!!splitItem.category && (