callstack / callstack/react-native-bottom-tabs
Custom TabBar on iPad still renders duplicate with Top Navigation (iPadOS 26+)
- Dominant language
- TypeScript
- Stars
- 1.5k
- Forks
- 109
- Avg merge
- 11h 44m
- Merged PRs (30d)
- 8
Description
### Before submitting a new issue
- [x] I tested using the latest version of the library, as the bug might be already fixed.
- [x] I tested using a [supported version](https://github.com/reactwg/react-native-releases/blob/main/docs/support.md) of react native.
- [x] I checked for possible duplicate issues, with possible answers.
### Bug summary
Top Navigation/Toolbar appears on iPad with iPadOS 26+ even with `headerShown: false` and custom bottom tab bar configured. **This worked correctly on iPadOS 18.5, but broke on iPadOS 26.**
## Expected behavior
Only the custom bottom tab bar should be visible. No top navigation toolbar should appear (as it did on iPadOS 18.5).
## Actual behavior
On iPad with iPadOS 26+, a new top navigation/toolbar appears despite:
- Setting `headerShown: false`
- Using custom bottom tab bar with `tabBarStyle={{ display: "none" }}`
- Custom tab bar positioned at bottom
**On iPadOS 18.5, this setup worked perfectly without the top toolbar.**
This appears to be a breaking change in the iPadOS 26 iPad UI pattern with a new segmented control.
## Question about PR #420 and iPadOS 26 compatibility
I'm using the latest `@bottom-tabs/monorepo` from GitHub (main branch) which includes the changes from PR #420. However, the top toolbar still appears on iPadOS 26.
PR #420 moves `.hideTabBar(props.tabBarHidden)` INSIDE each Tab's child view:
```swift
Tab(value: tabData.key, role: tabData.role?.convert()) {
child
.ignoresSafeArea(.container, edges: .all)
.tabAppear(using: context)
.hideTabBar(props.tabBarHidden) // Moved here in PR #420
} label: {
TabItem(title: tabData.title, icon: icon, sfSymbol: tabData.sfSymbol, labeled: props.labeled)
}
```
**Is PR #420 sufficient for iPadOS 26, or do we need an additional fix like `.toolbar(.hidden, for: .tabBar)` for iPadOS 26+ compatibility?**
I tested adding `.toolbar(.hidden, for: .tabBar)` after `.hideTabBar()` and it seems to hide the toolbar, but I want to confirm if this is the correct approach or if there's a better solution.
### Library version
- @bottom-tabs/monorepo: main branch (from GitHub) - includes PR #420
- @bottom-tabs/react-navigation: ^1.0.2
### Environment info
```shell
- react-native: 0.81.4
- expo: ~54.0.13
- iPadOS: 18.5 (works) → 26.0+ (broken - iPad Simulator)
- Platform: iPadOS
- Device: iPad Simulator
```
### Steps to reproduce
1. Create app with @bottom-tabs/react-navigation on Expo Router
2. Test on iPad with iPadOS 18.5 → Works
3. Test on iPad with iPadOS 26+ → Top toolbar appears
4. Expected: Toolbar should not appear on iPadOS 26 either
### Reproducible sample code
```js
# app/(tabs)/\_layout.tsx
import { withLayoutContext } from "expo-router";
import { createNativeBottomTabNavigator } from "@bottom-tabs/react-navigation";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { Platform, View, Pressable, StyleSheet, Image, Dimensions } from "react-native";
const BottomTabNavigator = createNativeBottomTabNavigator().Navigator;
const Tabs = withLayoutContext(BottomTabNavigator);
function CustomBottomTabBar({ state, descriptors, navigation }: any) {
const insets = useSafeAreaInsets();
return (
{state.routes.map((route: any, index: number) => {
const { options } = descriptors[route.key];
const isFocused = state.index === index;
const onPress = () => {
const event = navigation.emit({
type: "tabPress",
target: route.key,
canPreventDefault: true,
});
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name, route.params);
}
};
const iconSource = options.tabBarIcon
? options.tabBarIcon({
focused: isFocused,
color: isFocused ? "#007AFF" : "#999999",
size: 24,
})
: null;
return (
{iconSource && (
)}
);
})}
);
}
const styles = StyleSheet.create({
tabBar: {
position: "absolute",
left: 0,
right: 0,
bottom: 0,
flexDirection: "row",
backgroundColor: "#fff",
borderTopWidth: 1,
borderTopColor: "#e5e5e5",
height: 60,
},
tabItem: {
flex: 1,
justifyContent: "center",
alignItems: "center",
paddingVertical: 8,
},
tabItemActive: {
borderTopWidth: 2,
borderTopColor: "#007AFF",
},
});
export default function TabLayout() {
const isIPad = Platform.OS === "ios" && Dimensions.get("window").width >= 768;
return (
: undefined}
tabBarStyle={isIPad ? { display: "none" } : undefined}
>
(focused ? icons.compass : icons.compassOutline),
}}
/>
(focused ? icons.search : icons.searchOutline),
}}
/>
(focused ? icons.addCircle : icons.addCircleOutline),
}}
/>
(focused ? icons.flag : icons.flagOutline),
}}
/>
(focused ? icons.person : icons.personOutline),
}}
/>
);
}
```
Contributor guide
Assessment
This issue has not been assessed yet.