Capacitor Splash Screen and Status Bar
The first two seconds of your app are a splash screen and a status bar. Nobody thinks about either until a reviewer, or a client’s investor, opens the app on their phone and sees a white flash, then a logo stretched sideways, then the clock in the corner sitting on top of your header in black text on a navy background. It looks broken even when nothing is broken.
None of this is hard to fix in a Lovable app wrapped with Capacitor. It’s just fiddly, and the defaults are wrong for most designs.
The white flash
Here’s what happens with the default setup. The native splash shows, then the Capacitor splash screen plugin hides it after 500 ms because that’s the default launchShowDuration. But a Lovable app is a Vite and React bundle. 500 ms later the WebView has often not finished parsing your JavaScript, let alone checked the Supabase session. So the user sees a blank white WebView for a moment, then your login screen, then (if they’re already signed in) a jump to the dashboard.
Turn the auto hide off and hide it yourself when the app actually has something to show:
// capacitor.config.ts
plugins: {
SplashScreen: {
launchAutoHide: false,
backgroundColor: "#0b1020",
},
},
// main.tsx, or wherever your auth state first resolves
import { SplashScreen } from "@capacitor/splash-screen";
supabase.auth.getSession().finally(() => {
SplashScreen.hide();
});
Hide it after the session check, not after React mounts. Mounting is too early, you’d still flash the login page at people who are logged in. And always put a timeout fallback on it. We had one app sit on the splash forever for a tester in a hotel because getSession() was waiting on a refresh request over terrible wifi and never settled fast enough. A setTimeout(() => SplashScreen.hide(), 4000) next to it costs you nothing.
While you’re there, set the background-color on html and body in index.html to match the splash. If the splash is navy and the page is white, you get a white frame between them even with perfect timing.
Images that don’t look stretched
Don’t hand-make splash images for every screen size. Install @capacitor/assets, put a square icon-only.png (1024 px) and a splash.png (2732 by 2732, logo small and centered, lots of empty space) in an assets/ folder at the project root, and run npx @capacitor/assets generate. It writes the iOS and Android icons and splash files into the native projects for you. Add a splash-dark.png if you want a dark mode variant.
If you ever call SplashScreen.show() yourself on Android (some apps reuse it as a loading screen after login), the default androidScaleType is FIT_XY, which stretches the image to fill the screen. That’s where the squashed logos come from. Use CENTER_CROP.
Android 12 does its own thing
Since Capacitor 4 the launch splash on Android goes through the Android 12 Splash Screen API (with a compatibility library for Android 11 and older). That system splash is an icon on a solid color, not your full-screen image, and you can’t turn it off on Android 12 and newer because the OS owns it. Several of the plugin’s options, like showSpinner, androidScaleType and backgroundColor, simply don’t apply to that launch splash. People lose an afternoon tweaking config that Android is ignoring. Design for it instead: a clean icon that works small, on your brand color.
One more oddity. On Android 12 and 12L the splash doesn’t show at all when the app is launched from some third-party launchers or straight from Android Studio. Google fixed it in Android 13 and didn’t backport it. If your tester says “there’s no splash”, ask what phone.
The status bar
Install @capacitor/status-bar and the first thing that confuses everyone is the naming. Style.Dark means light text, for a dark background. Style.Light means dark text, for a light background. It describes the background, not the text. Every single time.
import { StatusBar, Style } from "@capacitor/status-bar";
await StatusBar.setStyle({ style: Style.Dark }); // white clock, for a dark header
On iOS, the plugin needs UIViewControllerBasedStatusBarAppearance set to YES in Info.plist, or your setStyle calls do nothing and you won’t get an error explaining why.
If your Lovable app has a dark mode toggle, call setStyle from the same place that flips the theme. Otherwise you get a black clock on a black header the first time someone switches.
Android 15 and edge-to-edge
This is the change that broke a lot of wrapped apps this year. Apps targeting Android 15 (API 35) are drawn edge to edge by default, which means your web content now goes under the status bar and the navigation bar. You could still opt out on 15 with windowOptOutEdgeToEdgeEnforcement. On Android 16 (API 36) with Capacitor 8 you can’t, and the status bar plugin’s overlaysWebView and backgroundColor options stop working completely. Google Play keeps raising the required target SDK, so you will hit this whether you planned to or not.
So the fix is in your CSS, not in config. Make sure the viewport meta tag in index.html has viewport-fit=cover, then pad for the safe areas:
body {
padding-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px));
padding-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px));
}
The --safe-area-inset-* variables come from Capacitor’s System Bars API, which ships inside @capacitor/core in Capacitor 8. Older Android WebViews (below version 140) report wrong values for the env() ones, so Capacitor injects correct values as CSS variables and you use env() as the fallback. That one detail fixed a client app where the bottom tab bar sat behind the gesture bar on a Pixel but looked fine on a Samsung.
Put the padding on the fixed header and the bottom nav, not just body, if they’re position: fixed. Lovable’s generated layouts usually are.
Honestly, I’d rather design every wrapped app for edge to edge from day one now, on iOS too, and stop fighting it. A colored header that extends under the clock looks better anyway.
If you’d rather someone else sweated the first two seconds, plus the other few hundred details between a Lovable web app and an approved store listing, get in touch. The packages are on the home page.