Summary
This postmortem analyzes a regression introduced after migrating to Expo SDK 54 with Liquid Glass where header buttons became misaligned, incorrectly colored, and inconsistently rendered between Expo Go and TestFlight. The issue stemmed from a combination of React Navigation + react-native-screens behavioral changes, deprecated styling APIs, and legacy dependencies overriding new defaults.
Root Cause
The failure originated from incompatible or outdated packages interacting with the new Liquid Glass header rendering pipeline in SDK 54.
Key contributing factors:
- react-native-screens v3/v4 behavior differs from the version bundled with SDK 54
- Header button containers are now rendered by native screens, not React Navigation’s JS layout
- Pressable and Ionicons no longer inherit backgroundColor or alignment the same way
- Expo Go uses a different dependency graph than your standalone build, so it hides version conflicts
- Old navigation options (headerStyle, headerTintColor, headerRightContainerStyle) no longer map 1:1 to the new Liquid Glass header
The result:
Your icon is being wrapped by a native container with its own padding, tinting, and background rules.
Why This Happens in Real Systems
This class of bug is extremely common during major SDK upgrades because:
- Native modules change faster than JS abstractions
- Breaking changes are often implicit, not documented
- Expo Go bundles newer dependencies, masking mismatches in your local project
- Navigation libraries rely heavily on native view hierarchy, so small changes ripple outward
- Old packages silently override new defaults, especially UI-related ones
Real systems suffer because UI frameworks evolve, but apps often carry years of accumulated dependencies.
Real-World Impact
Teams typically observe:
- Misaligned icons due to new native padding rules
- Unexpected background colors because Liquid Glass applies translucency + blur + tint
- Different behavior between Expo Go and TestFlight
- Hours wasted debugging “simple” styling issues
- Inconsistent UX across devices
These issues often block releases because navigation headers are highly visible.
Example or Code (if necessary and relevant)
Below is a minimal example of how senior engineers isolate the issue by removing all implicit styling and forcing explicit layout:
import { Pressable } from "react-native";
import { Ionicons } from "@expo/vector-icons";
export function EditIcon({ onPress }) {
return (
);
}
This works because it overrides the native container’s unpredictable layout.
How Senior Engineers Fix It
Experienced engineers follow a systematic approach:
1. Pin dependency versions
- react-native-screens
- @react-navigation/native
- @react-navigation/native-stack
- expo-router (if used)
They ensure all versions match the SDK 54 compatibility matrix.
2. Disable legacy screen behavior
Many teams fix header issues by forcing the new behavior:
import { enableScreens } from "react-native-screens";
enableScreens(true);
3. Override native header container styles
Use:
headerRightContainerStyleheaderLeftContainerStyleheaderTitleContainerStyle
Explicitly set:
paddingmarginbackgroundColor: "transparent"
4. Avoid relying on inherited tinting
Liquid Glass applies its own tint. Senior engineers explicitly set:
headerTintColorheaderStyle: { backgroundColor: "transparent" }
5. Test in a standalone build early
Expo Go is not a reliable representation of native behavior after SDK 50+.
6. Remove outdated packages
Any old UI or navigation-related package can override the new header behavior.
Why Juniors Miss It
Junior engineers typically struggle because:
- They assume Expo Go = production, which is no longer true
- They expect JS styling to override native styling, but headers are native
- They don’t know that react-native-screens controls header layout, not React Navigation
- They try to fix symptoms (alignment, color) instead of the root dependency mismatch
- They don’t inspect the native view hierarchy, where the real issue lives
- They underestimate how much SDK upgrades change navigation internals
The result is hours spent tweaking styles that the native layer simply ignores.
If you want, I can help you audit your package.json and identify exactly which dependency is causing the mismatch.