[iOS] Header Title Misaligned When First Stack Screen Has No Native Header
Description
When using a native stack navigator in React Native, if the first screen in the stack has the native header hidden (e.g., headerShown: false), the header title on subsequent screens appears misaligned or not centered. This only happens when navigating from a screen without a native header to one that uses the default header.
https://github.com/user-attachments/assets/812931b1-2bdb-4074-b9e3-8e6f2142dcd8
I think this might be related to view recycling. It was also happening on old architecture a couple of months ago while I was testing.
Steps to reproduce
-
Create a stack navigator with two screens:
- Screen A:
headerShown: false - Screen B: default header shown
- Screen A:
-
Navigate from Screen A to Screen B.
-
Observe that the header title on Screen B is not centered.
Make sure you cold start the app with header shown set to false for Screen A.
Snack or a link to a repository
https://snack.expo.dev/M-RIitAjPdl7xsK7AKYbQ
Screens version
4.11.1
React Native version
0.79.5
Platforms
iOS
JavaScript runtime
Hermes
Workflow
React Native (without Expo)
Architecture
Fabric (New Architecture)
Build type
Debug mode
Device
iOS simulator
Device model
iPhone 16
Acknowledgements
Yes
Hi, the snack You provided doesn't show the bug and my attempts are working correctly.
https://github.com/user-attachments/assets/b2db2c5e-189f-43ec-94e0-4b39b72b0d85
Here is the snippet I tried:
CLICK
import React from 'react';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
import {
NativeStackNavigationProp,
createNativeStackNavigator,
} from '@react-navigation/native-stack';
import { Button, Pressable, ScrollView, View } from 'react-native';
type RouteParamList = {
Home: undefined;
Second: undefined;
Third: undefined;
};
type NavigationProp<ParamList extends ParamListBase> = {
navigation: NativeStackNavigationProp<ParamList>;
};
type StackNavigationProp = NavigationProp<RouteParamList>;
const Stack = createNativeStackNavigator<RouteParamList>();
const Home = ({ navigation }: StackNavigationProp) => <>
<ScrollView contentInsetAdjustmentBehavior='automatic'>
<Button title="Open second" onPress={() => navigation.navigate('Second')} />
<Button title="Open third" onPress={() => navigation.navigate('Third')} />
</ScrollView>
</>
const Second = ({ navigation }: StackNavigationProp) => <>
<ScrollView contentInsetAdjustmentBehavior='automatic'>
<Button title="Back" onPress={() => navigation.goBack()} />
</ScrollView>
</>
const Third = ({ navigation }: StackNavigationProp) => <>
<ScrollView contentInsetAdjustmentBehavior='automatic'>
<Button title="Back" onPress={() => navigation.goBack()} />
</ScrollView>
</>
export default function App() {
return (
<>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={Home}
options={{
headerShown: false,
headerRight: () => <Pressable>
<View style={{ width: 20, height: 20, backgroundColor: 'red' }}></View>
</Pressable>,
headerTitle: "Your credit score"
}} />
<Stack.Screen
name="Second"
component={Second}
options={{
headerTransparent: true,
headerTitle: "Your expected interest rate"
}}
/>
<Stack.Screen
name="Third"
component={Third}
options={{
headerTransparent: true,
headerTitle: "Your expected mortgage interest rate"
}}
/>
</Stack.Navigator>
</NavigationContainer>
</>
);
}
Can You please give more info and/or modify the code above?
I am having this issue as well, one of my Stack.Screen's has a header and the Stack.Navigator has headerShown: false when I set headerShown to true the stack screen header behaves as normal.
@kmichalikk I noticed you didn't close and relaunch the app like @matinzd did. Maybe thats why it wasn't reproduced.
Yes try restarting the app process and try again! It works on hot the reload when it's initially set to true but not on cold start when it's set to false.
I will update the reproduction steps.
Sorry for not providing the proper snack example. I thought I saved it but you have the right code.
Thanks for clarification. Unfortunately I still don't see the bug. My attempt was as follows:
- blank app with
npx @react-native-community/cli@latest init headermisaligned --version 0.79.5 - install screens @ 4.11.1 and native-stack
- modify previous snipped with bottom tabs, useTranslation, blurEffect on header and some content on screen to better match the code on the recording
- use iPhone 16 iOS 18.5 emulator
- followed updated steps
please let me know what I'm missing
The code:
CLICK
import React from 'react';
import { NavigationContainer, ParamListBase } from '@react-navigation/native';
import {
NativeStackNavigationProp,
createNativeStackNavigator,
} from '@react-navigation/native-stack';
import { Button, Pressable, ScrollView, View, Text } from 'react-native';
import i18n from "i18next";
import { useTranslation, initReactI18next } from "react-i18next";
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
i18n
.use(initReactI18next) // passes i18n down to react-i18next
.init({
// the translations
// (tip move them in a JSON file and import them,
// or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
resources: {
en: {
translation: {
'credit-score': 'Your credit score',
'interest-rate': 'Your expected interest rate',
'mortgate-interest-rate': 'Your expected mortgage interest rate'
}
}
},
lng: "en", // if you're using a language detector, do not define the lng option
fallbackLng: "en",
interpolation: {
escapeValue: false // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
}
});
type RouteParamList = {
Home: undefined;
Second: undefined;
Third: undefined;
};
type NavigationProp<ParamList extends ParamListBase> = {
navigation: NativeStackNavigationProp<ParamList>;
};
type StackNavigationProp = NavigationProp<RouteParamList>;
const Stack = createNativeStackNavigator<RouteParamList>();
function Home({ navigation }: StackNavigationProp) {
return <>
<ScrollView contentInsetAdjustmentBehavior='automatic'>
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras non fermentum enim, in suscipit velit. Cras eu cursus nibh, sit amet pulvinar magna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Fusce vehicula dolor sit amet mi placerat hendrerit. Fusce risus felis, pulvinar ut auctor a, lacinia nec eros. Nulla mattis, lectus in sagittis convallis, quam elit condimentum risus, porta porttitor dui odio at orci. Ut nibh lectus, pulvinar sed purus quis, elementum viverra urna. Nulla vulputate sollicitudin sem, eu auctor tortor ullamcorper quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sed lectus a est aliquet dignissim. Cras interdum fringilla massa, sed mollis eros consectetur rutrum. Phasellus vel tortor mauris. Vivamus semper facilisis feugiat.
Nam pharetra, lacus sed aliquet lobortis, sem lorem porta nibh, quis sagittis risus augue vitae odio. Aliquam vel vulputate nibh. Donec velit lectus, pharetra a vehicula at, vestibulum eu risus. Nulla mattis tellus ut laoreet feugiat. Fusce sit amet eros nec justo tincidunt finibus eu et odio. Quisque aliquet rutrum urna in tincidunt. Nunc in consequat quam, eu volutpat quam. Praesent consequat ante vitae urna tempus commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam erat libero, tempor id augue id, ullamcorper ultricies ante.
Aliquam erat volutpat. Morbi viverra est augue, at laoreet ligula congue quis. Cras volutpat dui vitae neque eleifend, ac efficitur justo malesuada. Quisque mi ante, consequat sit amet mattis porttitor, aliquam ac tortor. Nullam luctus nec augue id porta. Mauris eget libero aliquet, finibus turpis et, dictum elit. Ut dolor orci, faucibus in sollicitudin eu, pretium vitae neque. Maecenas fermentum tincidunt justo, vitae finibus nunc iaculis eget.
Integer eget felis sem. Suspendisse purus nibh, interdum a maximus rutrum, dignissim et purus. Pellentesque eget velit viverra, porttitor dolor dignissim, vestibulum felis. Fusce mi elit, pellentesque sed magna sed, maximus malesuada neque. Sed auctor augue a purus posuere, vitae laoreet nisl elementum. Mauris vel aliquam neque. In vel nunc augue.
Duis vel vehicula lectus, ut iaculis libero. Sed tempor lorem vel ipsum posuere consectetur. Vivamus aliquam id nunc sed ultricies. Suspendisse pretium elementum maximus. Quisque dapibus libero quis varius congue. Duis ac maximus leo. Morbi commodo porta condimentum. Sed aliquam dolor eu est pulvinar porta. Nam fermentum mauris non urna mattis elementum. Cras et vulputate nunc, ut egestas nunc. Vestibulum fermentum, mi ut ornare venenatis, urna erat rhoncus felis, eget congue mauris nibh vestibulum felis.
</Text>
<Button title="Open second" onPress={() => navigation.navigate('Second')} />
<Button title="Open third" onPress={() => navigation.navigate('Third')} />
</ScrollView>
</>
}
function Second({ navigation }: StackNavigationProp) {
return <>
<ScrollView contentInsetAdjustmentBehavior='automatic'>
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras non fermentum enim, in suscipit velit. Cras eu cursus nibh, sit amet pulvinar magna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Fusce vehicula dolor sit amet mi placerat hendrerit. Fusce risus felis, pulvinar ut auctor a, lacinia nec eros. Nulla mattis, lectus in sagittis convallis, quam elit condimentum risus, porta porttitor dui odio at orci. Ut nibh lectus, pulvinar sed purus quis, elementum viverra urna. Nulla vulputate sollicitudin sem, eu auctor tortor ullamcorper quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sed lectus a est aliquet dignissim. Cras interdum fringilla massa, sed mollis eros consectetur rutrum. Phasellus vel tortor mauris. Vivamus semper facilisis feugiat.
Nam pharetra, lacus sed aliquet lobortis, sem lorem porta nibh, quis sagittis risus augue vitae odio. Aliquam vel vulputate nibh. Donec velit lectus, pharetra a vehicula at, vestibulum eu risus. Nulla mattis tellus ut laoreet feugiat. Fusce sit amet eros nec justo tincidunt finibus eu et odio. Quisque aliquet rutrum urna in tincidunt. Nunc in consequat quam, eu volutpat quam. Praesent consequat ante vitae urna tempus commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam erat libero, tempor id augue id, ullamcorper ultricies ante.
Aliquam erat volutpat. Morbi viverra est augue, at laoreet ligula congue quis. Cras volutpat dui vitae neque eleifend, ac efficitur justo malesuada. Quisque mi ante, consequat sit amet mattis porttitor, aliquam ac tortor. Nullam luctus nec augue id porta. Mauris eget libero aliquet, finibus turpis et, dictum elit. Ut dolor orci, faucibus in sollicitudin eu, pretium vitae neque. Maecenas fermentum tincidunt justo, vitae finibus nunc iaculis eget.
Integer eget felis sem. Suspendisse purus nibh, interdum a maximus rutrum, dignissim et purus. Pellentesque eget velit viverra, porttitor dolor dignissim, vestibulum felis. Fusce mi elit, pellentesque sed magna sed, maximus malesuada neque. Sed auctor augue a purus posuere, vitae laoreet nisl elementum. Mauris vel aliquam neque. In vel nunc augue.
Duis vel vehicula lectus, ut iaculis libero. Sed tempor lorem vel ipsum posuere consectetur. Vivamus aliquam id nunc sed ultricies. Suspendisse pretium elementum maximus. Quisque dapibus libero quis varius congue. Duis ac maximus leo. Morbi commodo porta condimentum. Sed aliquam dolor eu est pulvinar porta. Nam fermentum mauris non urna mattis elementum. Cras et vulputate nunc, ut egestas nunc. Vestibulum fermentum, mi ut ornare venenatis, urna erat rhoncus felis, eget congue mauris nibh vestibulum felis.
</Text>
<Button title="Back" onPress={() => navigation.goBack()} />
</ScrollView>
</>
}
function Third({ navigation }: StackNavigationProp) {
return <>
<ScrollView contentInsetAdjustmentBehavior='automatic'>
<Text>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras non fermentum enim, in suscipit velit. Cras eu cursus nibh, sit amet pulvinar magna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Fusce vehicula dolor sit amet mi placerat hendrerit. Fusce risus felis, pulvinar ut auctor a, lacinia nec eros. Nulla mattis, lectus in sagittis convallis, quam elit condimentum risus, porta porttitor dui odio at orci. Ut nibh lectus, pulvinar sed purus quis, elementum viverra urna. Nulla vulputate sollicitudin sem, eu auctor tortor ullamcorper quis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus sed lectus a est aliquet dignissim. Cras interdum fringilla massa, sed mollis eros consectetur rutrum. Phasellus vel tortor mauris. Vivamus semper facilisis feugiat.
Nam pharetra, lacus sed aliquet lobortis, sem lorem porta nibh, quis sagittis risus augue vitae odio. Aliquam vel vulputate nibh. Donec velit lectus, pharetra a vehicula at, vestibulum eu risus. Nulla mattis tellus ut laoreet feugiat. Fusce sit amet eros nec justo tincidunt finibus eu et odio. Quisque aliquet rutrum urna in tincidunt. Nunc in consequat quam, eu volutpat quam. Praesent consequat ante vitae urna tempus commodo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Nullam erat libero, tempor id augue id, ullamcorper ultricies ante.
Aliquam erat volutpat. Morbi viverra est augue, at laoreet ligula congue quis. Cras volutpat dui vitae neque eleifend, ac efficitur justo malesuada. Quisque mi ante, consequat sit amet mattis porttitor, aliquam ac tortor. Nullam luctus nec augue id porta. Mauris eget libero aliquet, finibus turpis et, dictum elit. Ut dolor orci, faucibus in sollicitudin eu, pretium vitae neque. Maecenas fermentum tincidunt justo, vitae finibus nunc iaculis eget.
Integer eget felis sem. Suspendisse purus nibh, interdum a maximus rutrum, dignissim et purus. Pellentesque eget velit viverra, porttitor dolor dignissim, vestibulum felis. Fusce mi elit, pellentesque sed magna sed, maximus malesuada neque. Sed auctor augue a purus posuere, vitae laoreet nisl elementum. Mauris vel aliquam neque. In vel nunc augue.
Duis vel vehicula lectus, ut iaculis libero. Sed tempor lorem vel ipsum posuere consectetur. Vivamus aliquam id nunc sed ultricies. Suspendisse pretium elementum maximus. Quisque dapibus libero quis varius congue. Duis ac maximus leo. Morbi commodo porta condimentum. Sed aliquam dolor eu est pulvinar porta. Nam fermentum mauris non urna mattis elementum. Cras et vulputate nunc, ut egestas nunc. Vestibulum fermentum, mi ut ornare venenatis, urna erat rhoncus felis, eget congue mauris nibh vestibulum felis.
</Text>
<Button title="Back" onPress={() => navigation.goBack()} />
</ScrollView>
</>
}
function TestTab() {
const { t } = useTranslation();
return (
<>
<Stack.Navigator screenOptions={{
headerTransparent: true,
headerBlurEffect: 'systemThinMaterial'
}}>
<Stack.Screen
name="Home"
component={Home}
options={{
headerShown: false,
headerRight: () => <Pressable>
<View style={{ width: 20, height: 20, backgroundColor: 'red' }}></View>
</Pressable>,
headerTitle: t('credit-score')
}} />
<Stack.Screen
name="Second"
component={Second}
options={{
headerTitle: t('interest-rate')
}}
/>
<Stack.Screen
name="Third"
component={Third}
options={{
headerTitle: t('mortgate-interest-rate')
}}
/>
</Stack.Navigator>
</>
);
}
function TestTab2() {
return <Text>Test Tab 2</Text>
}
const Tab = createBottomTabNavigator();
export default function App() {
return (<>
<NavigationContainer>
<Tab.Navigator screenOptions={{
headerShown: false
}}>
<Tab.Screen name="Tab1" component={TestTab}/>
<Tab.Screen name="Tab2" component={TestTab2}/>
</Tab.Navigator>
</NavigationContainer>
</>)
}
The recording:
https://github.com/user-attachments/assets/317bda11-fdec-4b23-9d9c-0e32d03bf4a2
Im also experiencing this. I get two cases of it appearing incorrect.
- Its miss-aligned (top left instead of center)
- It overflows the header using transformY
It only happens on any screen navigated to from a screen with headerShown: false
Experiencing the same. It only happens on any screen navigated to from a screen with headerShown: false.
React Native version: 0.80.1
For the record, we're using headerBackButtonDisplayMode: 'minimal' and headerBackTitle: '' in the screen navigated to from a screen with headerShown: false
Update: Using headerBackButtonDisplayMode: 'minimal' and adding a headerBackTitle text seems to solve the issue, but then we can't prevent showing the "Back" button title anymore.
The way I solved it was setting headerMode: 'screen', in the Stack.Screen component that has the header enabled.
I also can confirm this issue; navigation from a screen with headerShown: false to a screen with headerShown: true will mess up with the destination screens title alignment.
I also have this issue. Navigating from a screen with headerShown: false to one with headerShown: true messes up the title alignment. I'm using headerBackTitleVisible: false on the latter screen; changing that to true, or instead using headerBackTitleStyle: { fontSize: 0 } fixes the problem.
Okay, I've had the opportunity to look at it again. The hint from @nlasagni about headerBackButtonDisplayMode was the missing piece. Now the bug is present, and size of the offset appears to correlate with the title of the screen navigated from.
https://github.com/user-attachments/assets/0fcc65d3-c3f5-4684-81a4-6d27d6b52219
The title works correctly on iOS 26.
@matinzd could you confirm that the above prop was set in your app? Is there any other setting we should be aware of? We can only attempt to fix what we are able to see locally and you didn't provide any working example for us to run.
I have the same issue when I set headerBackTitleVisible: false, and it started to reproduce when I changed the font size to bigger in phone accessibility settings