Skip to content

Conversation

@kmichalikk
Copy link
Contributor

@kmichalikk kmichalikk commented Dec 18, 2025

Closes https://github.com/software-mansion/react-native-screens-labs/issues/503

Description

This PR adds testId and accessibilityLabel support for BottomTabsScreen and Item. For iOS, this maps to accessibilityIdentifier and accessibilityLabel, respectively. For android, things are more complicated when it comes to the item (the screen is handled automatically by react native in BaseViewManager):

  • testId is mapped to tag since this is what appeared to be working with Detox, which uses espresso under the hood
  • accessibilityLabel is mapped to the part of the contentDescription that overwrites tab title. With tab badge present, the final description is the title and badge description combined. Detox seems to be matching the parts of the description correctly with only the title/label as the selector. This prop is only available on android on API level 26 and up.

Changes

Added 4 props to the bottom tab configuration object:

  • screenTestId
  • tabBarItemTestId
  • screenAccessibilityLabel
  • tabBarItemAccessibilityLabel

Added nativeBottomTabs.e2e.ts test.

Test code and steps to reproduce

Run the e2e test. Use e2e-utils from https://github.com/software-mansion/react-native-screens/pull/3430/changes#diff-04033cb65a060a8211fbcd7be662a11be0f32c9b98d2f0d510c489e4e74af450, otherwise iOS won't complete.

@kmichalikk kmichalikk marked this pull request as draft December 18, 2025 09:50
@kmichalikk kmichalikk self-assigned this Dec 18, 2025
@kmichalikk kmichalikk added Type: Feature Issue or PR with feature request or implementation Area: Tabs Issue is related to native bottom tabs labels Dec 18, 2025
@kmichalikk kmichalikk marked this pull request as ready for review December 18, 2025 10:21
Copy link
Member

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job! The code looks good here, I have just few remarks regarding it's exact structure. Please answer them.

tabBarItem = [[UITabBarItem alloc] init];
}

tabBarItem.accessibilityIdentifier = _tabBarItemTestId;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good, but I think it is in wrong place.

We should keep the flow. Instead of setting these props here, the intention of update should be propagated to tab bar controller (see how appearance is handled) and then actual props should be updated in RNSTabBarController.reactMoutingTransactionDidMount, e.g. after appearance pass is done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

88ef668

let me know if it could be implemented this way?

@kmichalikk kmichalikk force-pushed the @kmichalikk/bottom-tabs-testing-props branch from 35384a8 to fe47106 Compare December 22, 2025 11:23
@kmichalikk kmichalikk requested a review from kkafar December 22, 2025 13:14
@kmichalikk
Copy link
Contributor Author

kmichalikk commented Dec 23, 2025

I'm not 100% positive we don't need to recreate the tab item when changing the id / label.

Please check and let me know if adding the following code to bottom tabs' index.tsx to update the props programmatically after 2s actually changes the props (on iOS, with accessibility inspector).

  const [tabConfigs, setTabConfigs] = React.useState(TAB_CONFIGS);

  useEffect(() => {
    setTimeout(() => {
      setTabConfigs([
        { ...TAB_CONFIGS[0], tabScreenProps: { ...TAB_CONFIGS[0].tabScreenProps, tabBarItemTestID: 'pomidor0', tabBarItemAccessibilityLabel: 'pomidor0' } },
        { ...TAB_CONFIGS[1], tabScreenProps: { ...TAB_CONFIGS[1].tabScreenProps, tabBarItemTestID: 'pomidor1', tabBarItemAccessibilityLabel: 'pomidor1' } },
        { ...TAB_CONFIGS[2], tabScreenProps: { ...TAB_CONFIGS[2].tabScreenProps, tabBarItemTestID: 'pomidor2', tabBarItemAccessibilityLabel: 'pomidor2' } },
        { ...TAB_CONFIGS[3], tabScreenProps: { ...TAB_CONFIGS[3].tabScreenProps, tabBarItemTestID: 'pomidor3', tabBarItemAccessibilityLabel: 'pomidor3' } },
      ])
    }, 2000);
  }, []);

...

      <BottomTabsContainer
        tabConfigs={tabConfigs}

Copy link
Member

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, and seems to work. Thank you!

I've left few more important remarks that need to be answered before we can land these changes.

Comment on lines +389 to +397
/**
* @summary testID for the BottomTabScreen
*/
testID?: string;

/**
* @summary accessibilityLabel for the BottomTabScreen
*/
accessibilityLabel?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you define testID & accessibilityLabel for the TabScreen itself, however please note that TabScreen Android implementation does not inherit form ReactViewGroup. How does it work then? Could you describe it in PR description, please?

Is is the question of ReactViewManager using the accessibility delegate & not relying on state defined explicitly in ReactViewGroup?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK this is set by BaseViewManager which TabScreenViewManager extends somehow

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. So it works provided that BaseViewManager does not rely on any state defined explicitly in ReactViewGroup. This is not perfect, but I guess we should be fine.

Can we please add a note to PR description that this is the case? (that we rely on ReactAndroid here).

Comment on lines 20 to 21
@property (nonatomic, nullable) NSString *tabItemTestID;
@property (nonatomic, nullable) NSString *tabItemAccessibilityLabel;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are these properties for here? If they are already defined on screen, then do we need to duplicate those here?

I see them being set in - [RNSBottomTabsScreenComponenView updateProps:oldProps] however I don't see a purpose. Could you explain it please?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see them being defined anywhere else? I asked if it could be implemented this way here: #3497 (comment)

Comment on lines 438 to 440
if (tabBarItemNeedsA11yUpdate) {
[_controller updateTabItemA11yProps];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you wanna cleanup the invalidated flag here?

Comment on lines 438 to 440
if (tabBarItemNeedsA11yUpdate) {
[_controller updateTabItemA11yProps];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this update should be deferred & only signalised to the controller. Same as appearance update or orientation update. Can we please align that?

I see now, that we haven't done so for scrollview edgeeffects for some reason. It should be subject of refactor in follow up PR, can we create ticket for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the same, because here we want to update a specific item, and other deferred ones update the whole tabBar. I'd need to pass some id to get the item again in TabBarController, and I'm not sure it would look good.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add state do the screen view controller (or just expose a property on controller & read the state from screen view) that can be read in the main loop of appearance coordinator to decide whether update is required or not.

I think this might be the way to go.

@kmichalikk kmichalikk force-pushed the @kmichalikk/bottom-tabs-testing-props branch from 7d55725 to 7c387af Compare January 7, 2026 06:55
Copy link
Member

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Responded to your concern.

Comment on lines 438 to 440
if (tabBarItemNeedsA11yUpdate) {
[_controller updateTabItemA11yProps];
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add state do the screen view controller (or just expose a property on controller & read the state from screen view) that can be read in the main loop of appearance coordinator to decide whether update is required or not.

I think this might be the way to go.

Copy link
Contributor

@kligarski kligarski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code looks good, I left 2 suggestions.

Copy link
Member

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Thank you.

@kmichalikk kmichalikk merged commit 3c8d850 into main Jan 12, 2026
9 checks passed
@kmichalikk kmichalikk deleted the @kmichalikk/bottom-tabs-testing-props branch January 12, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Tabs Issue is related to native bottom tabs Type: Feature Issue or PR with feature request or implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants