Migration to Expo SDK 54 and liquid glass header buttons

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:

  • headerRightContainerStyle
  • headerLeftContainerStyle
  • headerTitleContainerStyle

Explicitly set:

  • padding
  • margin
  • backgroundColor: "transparent"

4. Avoid relying on inherited tinting

Liquid Glass applies its own tint. Senior engineers explicitly set:

  • headerTintColor
  • headerStyle: { 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.

Leave a Comment