react-navigation header has a faint line

This turned into a massive adventure and subsequently, a very large and messy post, so to spare you from having to read through the mess that I keyboard-farted out below, here's a summary:

Main goal: To find a way to selectively show the navigation bar in my app without also selectively removing the Safe area padding to prevent my content being clipped by the iPhone X notch.

Why I did it the way I did: I wanted to try and use the Navigation Bar's built-in safe area padding instead of react-native's <SafeAreaView> because the <SafeAreaView> would interfere with the built in padding and cause the headers to be unnecessarily tall.

Side effect of not doing it the standard way: There was a faint line between the header (shown in picture with height: 0) and content that was plainly visible when both header and content were the same color

thin line on iPhone X simulator

What I tried:

  • Using <SafeAreaView>
  • Not using <SafeAreaView> and trying to header: null instead (aka "the recommended way of doing it")
  • Playing with header options to make the header disappear without causing clipped content. As a side affect (quoted from extended post below)

    i also found that header: null is functionally the same in result as headerTransparent: 'true' which I later discovered, is also functionally the same result as display: 'none'. All seem to be valid ways of making the entire header, Safe area padding and all, disappear.

  • Playing with the borderColor and borderWidth to determine if it was a border (in hindsight i should have done this more thoroughly)

  • Playing with 'opacity'
  • Shifting both the header and content around with position: 'relative', top: 6
  • Re-creating the issue in Xcode to see if it was an iOS issue (hindsight: i did this wrong)

This leads pretty cleanly into the answer so i'll cut it off here and continue it in my answer.


The long, confusing, original version

I'm using react-navigation in my react-native project (created with CRNA) and I think I have found a way to style the headers of a StackNavigator so that iOS takes care of the safe areas for you and negates the need to use <SafeAreaView> (which was interfering with the header on other screens).

My app is arranged such that the first screen is sort of like a home screen with a grid of buttons which will eventually each have their own page in the StackNavigator. On this home screen I want to disable the header bar since there is nothing to navigate to and it just moves the content down, however, on the other screens, I want to have the header bar visible to display the back button. While using <SafeAreaView> I could only get one of the two mentioned above, either the home screen was clipped by the notch on the iPhone X, or the <SafeAreaView> moved the navigation/header bar on the other screens down and takes up space.

However, I found that by ditching the <SafeAreaView> entirely, i was able to re-create what I wanted using header styling from react-navigation:

App.js

const Navigation = createStackNavigator({
    Home: HomeScreen,
    Test: PageScreen,
},
{
  headerMode: 'screen',
  navigationOptions: {
    headerStyle: { backgroundColor: colors.background},
    headerTintColor: colors.headerTint,
    headerTitleStyle: {
      fontWeight: 'bold',
    },
  }
});

HomeScreen.js

 static navigationOptions = {
    headerStyle: {
        height: 0, 
        backgroundColor: colors.background //had to re-color it here too because otherwise the header would be white???
      },
 };

This works well because it effectively removes the header from the home screen but still keeps the safe area padding that the header creates. However, there is a thin line now separating the header from the Home Screen content and i'm not sure how to make it go away. It is the only thing that gives away my header trickery and detracts from the user experience.

Here is what I'm talking about: thin line on iPhone X simulator

This does not affect the other screens in which i want the header visible: Apparently it actually does, the colors were just masking it...

Other screens unaffected

nevermind they actually are

This line also appears on my iPhone 8 while testing the app using expo, although it is not as far down due to the smaller status bar on that device.

I have already tried changing the headerTintColor in the navigationOptions because I thought there may be some kind of background color behind the one I set but apparently that controls the color of the text and back button. The internet also doesnt seem to have much on this from what I can immediately tell as searches for header line react-navigation come up with results about hiding the header, being unable to hide the header, and how to customize the header. Does anyone know what this line is and how I remove it? or failing that, how to make <SafeAreaView> play nice with a StackNavigator's navigation header?

EDIT: I have also already tried the header: null, solution, however, by removing the header on the homepage of the app and not others causes a weird side effect with the <SafeAreaView> where the <SafeAreaView>'s safe area adds onto the one already built into the iOS header, causing the header to be a lot larger than it should, which is why I opted to go for height: 0 instead, since it renders the header effectively invisible, yet still keeps it's safe area.

EDIT 2: after playing with the borderColor and borderWidth of the header, I have determined that this line is not a border...

headerStyle: {
        height: 400,
        backgroundColor: 'green',
        borderColor: 'red',
        borderWidth: 150,
      },

Result of code above

EDIT 3: more findings. After playing with the CSS I have found that setting the opacity: 0 reveals a white layer behind the navigation bar... maybe this is slightly bigger than the navbar itself?

also, it seems like header: null is functionally the same in result as headerTransparent: 'true' which I later discovered, is also functionally the same result as display: 'none'. All seem to be valid ways of making the entire header, Safe area padding and all, disappear.

shifting it with position: 'relative', top: 6 seems to not solve the issue either

EDIT 4: Since I have determined that this line was in fact not just appearing on the screen where i'd set the header height to 0 and instead was appearing on all screens, I think this is something that is intentionally built into iOS in order to differentiate the header from the page content.

EDIT 4a: to confirm this theory I created a native-only xcode project and tried to recreate this by setting the nav bar and the view to black and, in contrary to my expectations, the issue did not re-create itself. So this is indeed something created by react-native and not built-into iOS i stand corrected again... How to remove navigation bar border/shadow?

screenshot


Continued from summary at the start of the question...

Once I realized my Xcode test was flawed i started googling for iOS only posts on StackOverflow with the same issue:

  • How to remove navigation bar border/shadow?
  • How to hide UINavigationBar 1px bottom line

Since these were iOS native solutions only I started looking for ways to re-create them in react. This then led me to looking for the same iOS only problem but with react keywords when I found the solution:

How do I hide the shadow under react-navigation headers?

While this question seems to be targeted at android, the answers also mention iOS:

elevation: 0, // remove shadow on Android shadowOpacity: 0, // remove shadow on iOS

Source: https://stackoverflow.com/a/42709731

I tried this. It didnt work.

Then further down an answer said this:

headerStyle: { elevation: 0, shadowOpacity: 0, borderBottomWidth: 0, }

Tried it: It worked.


TL;DR

If you want to hide the header while using a StackNavigator in modern versions of react native without having to use a <SafeAreaView>, use the following code either in createStackNavigator({...}) or in your Screen classes' static navigationOptions = {...}:

headerStyle: {
        backgroundColor: colors.background,
        borderBottomWidth: 0,
        height: 0,
    },

In navigation v5, in your stack screen add in your options object:

 <Stack.Screen
      name="Whatever"
      component={SomeScreen}
      options={{
        title: "Whatever",
        headerTitleStyle: { fontSize: 22, color: "#fff" },
        headerStyle: { shadowColor: "transparent" } // This is the important bit
    }}
    // whatever else you need in the screen header
  />

Use { elevation: 0 } as a headerStyle screen-option. I confirmed this works in React Navigation V5. I think this (or similar) may work in React Navigation V4 as well.

<Stack.Navigator
  screenOptions={{
    headerStyle: { elevation: 0 }
  }}
  <Stack.Screen
    name="Home"
    component={HomeScreen} 
  />
</Stack.Navigator>

headerStyle:{ elevation:0,}

Work fine


As an update for v5, what worked for me was headerHideShadow: true.

So in context:

<LibraryStack.Navigator
  screenOptions={{
    headerHideShadow: true
  }}
>

Update: This is only for the native-stack (i.e. 'createNativeStackNavigator'). Leaving this behind in case someone using the native-stack still lands on this question.

More information (both for native-stack and stack): https://github.com/react-navigation/react-navigation/issues/6899