After I shipped one of my first Rork apps, a friend texted me: "I can't see the battery icon on the home screen." The white status bar icons had blended into a light background and disappeared. The Rork-generated code rarely touches StatusBar concerns, and once you start adding navigation, the bar can flicker, lose its background color on Android, or revert to system defaults the moment a modal opens.
This post walks through the StatusBar problems I have actually run into when shipping React Native apps built with Rork, and how I fix each one. We will cover the difference between expo-status-bar and the React Native built-in component, the per-screen patterns that actually survive navigation, and the small cross-platform tweaks that keep your app looking polished.
First Check: Which StatusBar Library Are You Using?
Rork-generated screens almost always include one of two imports:
import { StatusBar } from 'expo-status-bar'— the recommended path on Expo SDK 50+import { StatusBar } from 'react-native'— the built-in component with bigger platform differences
I default to expo-status-bar everywhere, and only reach for the React Native built-in when I need to set an Android background color. The Expo wrapper smooths over the iOS/Android differences, so I recommend exhausting that path first.
If both imports end up in the same project, they reference different internal instances, and your declarations cancel each other out in confusing ways. Pick one and stick with it.
Case 1: Status Bar Icons Disappear Into the Background
This is the most common failure: white icons on a light header, or black icons on a dark header. Time, battery, and signal become illegible.
// app/(tabs)/index.tsx
import { StatusBar } from 'expo-status-bar';
import { View, Text } from 'react-native';
export default function HomeScreen() {
return (
<View style={{ flex: 1, backgroundColor: '#FFFFFF' }}>
{/* Light background → render icons in dark */}
<StatusBar style="dark" />
<Text>Hello</Text>
</View>
);
}The style prop accepts three values:
light— render icons in white (use on dark backgrounds)dark— render icons in black (use on light backgrounds)auto— follow the system color scheme (often good enough)
When you have screens with different backgrounds — say a dark Settings screen and a light Home screen — declare <StatusBar /> inside each Screen component rather than once in _layout.tsx. expo-status-bar honors the most recently mounted declaration, so a single top-level component will visibly drift as you navigate.
Case 2: Android Ignores the Background Color
iOS does not have a real "status bar background" — the screen view simply shows through. Android, by contrast, owns its own region, and unless you set a color it stays the system default (usually black).
import { StatusBar } from 'expo-status-bar';
import { Platform, StatusBar as RNStatusBar, View } from 'react-native';
export default function ScreenWithColoredBar() {
return (
<View style={{ flex: 1 }}>
{/* Let expo-status-bar handle iOS */}
<StatusBar style="light" />
{/* Set the background only on Android */}
{Platform.OS === 'android' && (
<RNStatusBar backgroundColor="#1E3A8A" barStyle="light-content" />
)}
{/* screen content */}
</View>
);
}I use a hybrid setup: expo-status-bar for the foreground style on both platforms, and the built-in StatusBar only for the Android background color. This pattern survives the Android 15 edge-to-edge changes, but if you are targeting that surface seriously, look at adopting react-native-edge-to-edge.
Case 3: Color Flickers During Screen Transitions
You may see the previous screen's bar style linger for a frame during a tab switch or stack push. This happens because StatusBar updates lag the navigation animation.
If you are on Expo Router, pair useFocusEffect with expo-status-bar so the bar refreshes the moment the screen becomes active.
import { useFocusEffect } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { useCallback, useState } from 'react';
import { View } from 'react-native';
export default function SettingsScreen() {
const [style, setStyle] = useState<'light' | 'dark'>('dark');
// Re-declare every time this screen gains focus
useFocusEffect(
useCallback(() => {
setStyle('light');
return () => {
// No-op: the next screen will redeclare
};
}, []),
);
return (
<View style={{ flex: 1, backgroundColor: '#0F172A' }}>
<StatusBar style={style} animated />
{/* ... */}
</View>
);
}The animated prop fades between styles and softens the flicker. For apps where any visual jitter feels wrong (banking, healthcare), I leave it off — the snap actually reads as "responsive." For content apps I keep it on.
Case 4: A Modal Resets the Bar to the System Default
Opening a Modal often reverts StatusBar to the OS default, because Modal mounts in its own window and your parent declaration does not reach it.
import { Modal, View } from 'react-native';
import { StatusBar } from 'expo-status-bar';
import { useState } from 'react';
export default function ScreenWithModal() {
const [open, setOpen] = useState(false);
return (
<View style={{ flex: 1 }}>
<StatusBar style="dark" />
<Modal visible={open} onRequestClose={() => setOpen(false)}>
<View style={{ flex: 1, backgroundColor: '#000' }}>
{/* Re-declare inside the Modal */}
<StatusBar style="light" />
{/* modal content */}
</View>
</Modal>
</View>
);
}Always re-declare StatusBar inside the modal. You do not need an explicit reset on close — the parent declaration takes effect again automatically.
This works cleanly with iOS presentationStyle="fullScreen". The pageSheet and formSheet styles are managed by the OS, so your StatusBar declaration is ignored. Choose presentation style based on visual goals first.
Case 5: SafeAreaView Layout Breaks Under translucent
The StatusBar story is tightly coupled with how SafeAreaView calculates insets. With translucent enabled, your content extends behind the bar, and a header without proper padding can get clipped.
When you turn on translucent in expo-status-bar, you also need to add top padding from useSafeAreaInsets to account for the missing reserved space.
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
import { Platform, View } from 'react-native';
export default function TranslucentBar() {
const insets = useSafeAreaInsets();
return (
<View style={{ flex: 1 }}>
<StatusBar style="light" translucent />
<View
style={{
paddingTop: Platform.OS === 'android' ? insets.top : 0,
backgroundColor: '#1E3A8A',
}}
>
{/* header */}
</View>
</View>
);
}Whether you adopt translucent is mostly a design call. Modern Material You leans heavily on transparent system bars, so I default to translucent for new apps. For deeper background on safe areas, see The Safe Area and Notch Display Fix Guide for Rork.
Debugging Order
If you are not sure which case applies, here is the triage I run in order:
- Open the same screen on both an iOS Simulator and an Android Emulator
- Broken on both → you probably have no
<StatusBar />declaration, or it is stuck onauto - Broken only on Android → suspect a missing
backgroundColoror unexpectedtranslucent - Broken only on iOS → suspect a presentation style on a Modal
- Only flickers during navigation → declare per-screen instead of globally
- For broader cross-platform divergence, the iOS vs Android Behavior Difference Checklist for Rork is a useful companion
A quick tip: pressing Cmd + Shift + H in the iOS Simulator returns to the home screen and resets StatusBar state, which makes the fix easy to verify. On Android, switching the system theme inside the Emulator's Settings app surfaces interactions you will not catch in normal use.
Tools and Practices That Save You Time
A few small habits have removed almost all of my StatusBar regressions over time, and I want to share them because they are easy to adopt before you ship.
The first habit is to bake StatusBar into your screen template. When I create a new screen, I copy from a saved snippet that already has <StatusBar style="auto" /> near the top of the return statement. If the design later calls for a fixed style, I change auto to dark or light. Since the declaration is always there, the dropped-StatusBar bug never reaches production.
The second habit is to test on a real device early. The iOS Simulator and Android Emulator render StatusBar correctly most of the time, but the moment you stream a video, take a screenshot, or rotate the device on physical hardware, you sometimes see issues that never appear in the simulator. I now run a full status-bar review on a physical iPhone and a budget Android phone before every App Store submission. The cost is twenty minutes; the cost of a public review that calls out invisible icons is much higher.
The third habit is to script the audit. I keep a small Detox or Maestro flow that visits every top-level screen in sequence and takes a screenshot. Skimming forty thumbnails takes about a minute, and any contrast issue jumps out immediately. If you do not have automated UI tests yet, even a manual run-through with Control Center pulled down on iOS will surface most problems quickly.
If you find yourself fighting the same bug across multiple Rork projects, that is a signal to extract a shared component. I keep a ThemedStatusBar wrapper in my private template repository that takes a single theme prop ("light" | "dark") and handles the iOS/Android branching internally. It is twenty lines of code, but it has paid for itself many times over.
Wrapping Up
StatusBar is unglamorous, but the wrong color is the kind of detail that makes an app feel sloppy on first launch. The single biggest move is to standardize on expo-status-bar and add one line — <StatusBar style="..." /> — to every Screen component. That alone resolves Cases 1 and 2 for most projects.