Skip to content

Custom TabBar on iPad still renders duplicate with Top Navigation (iPadOS 26+) #463

@dennisjonda

Description

@dennisjonda

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

Environment info

- 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

# 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>
  );
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions