-
Notifications
You must be signed in to change notification settings - Fork 72
Description
Before submitting a new issue
- I tested using the latest version of the library, as the bug might be already fixed.
- I tested using a supported version of react native.
- 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:
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 fix tabbar showing duplicates #420
- @bottom-tabs/react-navigation: ^1.0.2
Environment info
- react-native: 0.81.4
- expo: ~54.0.13
- iPadOS: 18.5 (works) → 26.0+ (broken - iPad Simulator)
- Platform: iPadOS
- Device: iPad SimulatorSteps to reproduce
- Create app with @bottom-tabs/react-navigation on Expo Router
- Test on iPad with iPadOS 18.5 → Works
- Test on iPad with iPadOS 26+ → Top toolbar appears
- Expected: Toolbar should not appear on iPadOS 26 either
Reproducible sample code
# 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 (
<View style={[styles.tabBar, { paddingBottom: Math.max(insets.bottom, 8) }]}>
{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 (
<Pressable
key={route.key}
onPress={onPress}
style={[styles.tabItem, isFocused && styles.tabItemActive]}
>
{iconSource && (
<Image
source={iconSource}
style={{ width: 24, height: 24 }}
resizeMode="contain"
/>
)}
</Pressable>
);
})}
</View>
);
}
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 (
<Tabs
screenOptions={{
headerShown: false,
sceneStyle: { paddingBottom: isIPad ? 68 : 0 },
}}
tabBar={isIPad ? (props) => <CustomBottomTabBar {...props} /> : undefined}
tabBarStyle={isIPad ? { display: "none" } : undefined}
>
<Tabs.Screen
name="discover"
options={{
title: "Entdecken",
tabBarIcon: ({ focused }) => (focused ? icons.compass : icons.compassOutline),
}}
/>
<Tabs.Screen
name="browse"
options={{
title: "Stöbern",
tabBarIcon: ({ focused }) => (focused ? icons.search : icons.searchOutline),
}}
/>
<Tabs.Screen
name="add"
options={{
title: "Hinzufügen",
tabBarIcon: ({ focused }) => (focused ? icons.addCircle : icons.addCircleOutline),
}}
/>
<Tabs.Screen
name="reports"
options={{
title: "Meldungen",
tabBarIcon: ({ focused }) => (focused ? icons.flag : icons.flagOutline),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: "Profil",
tabBarIcon: ({ focused }) => (focused ? icons.person : icons.personOutline),
}}
/>
</Tabs>
);
}