How to: add a native splash screen
You want a native launch screen shown before JS ever runs, hidden once your app is ready. This
is @symbiote-native/splash-screen — a wrapper
over react-native-bootsplash reachable from React, Vue, Angular, and Svelte alike.
1. Install
Section titled “1. Install”npm install @symbiote-native/splash-screenOnly this package — never react-native-bootsplash directly. @symbiote-native/splash-screen
depends on it and ships as the sole autolinked native proxy (react-native.config.cjs on
Android, a podspec on iOS), so your app’s package.json never names the underlying library.
2. Generate the native binary (assets + native config)
Section titled “2. Generate the native binary (assets + native config)”The package’s symbiote-splash-screen bin is a thin passthrough to react-native-bootsplash’s
own asset generator — same flags, same output, zero reimplementation:
npx symbiote-splash-screen generate ./logo.png \ --background=#0f1e30 \ --logo-width=100 \ --assets-output=assets/bootsplashThis single command writes, in one pass:
-
assets/bootsplash/manifest.json— the JS-side manifestuseHideAnimationreads, matchingIManifestexactly:{"background": "#0f1e30","logo": { "width": 100, "height": 100 }}plus the logo image at every density (
logo.png,logo@2x.png,logo@3x.png, …). -
Android — a
Theme.BootSplash-derived style inres/values/styles.xmland abootsplash_logodrawable:<style name="BootTheme" parent="Theme.BootSplash"><item name="bootSplashBackground">@color/bootsplash_background</item><item name="bootSplashLogo">@drawable/bootsplash_logo</item><item name="postBootSplashTheme">@style/AppTheme</item></style> -
iOS — a
BootSplash.storyboardfile alongside the app’s existing storyboards.
Re-run the same command (with --brand/--dark-* if you have a generator license key) any time
the logo or colors change — it’s idempotent, not a one-shot scaffold.
3. Wire the generated theme into your native entry points
Section titled “3. Wire the generated theme into your native entry points”The generator does not do this part — it writes the theme/storyboard, but your app’s own
MainActivity/AppDelegate/Info.plist still have to reference them.
Android — MainActivity.kt
Section titled “Android — MainActivity.kt”Call RNBootSplash.init in onCreate, before super.onCreate:
import android.os.Bundleimport com.zoontek.rnbootsplash.RNBootSplash
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) { RNBootSplash.init(this, R.style.BootTheme) super.onCreate(savedInstanceState) }
// ...}iOS — AppDelegate.swift + Info.plist
Section titled “iOS — AppDelegate.swift + Info.plist”Call RNBootSplash.initWithStoryboard from customize(_:):
import RNBootSplash
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func customize(_ rootView: RCTRootView) { super.customize(rootView) RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView) }}Then point Info.plist’s launch-screen key at the generated storyboard:
<key>UILaunchStoryboardName</key><string>LaunchScreen</string><string>BootSplash</string>Run pod install inside ios/ after adding the dependency — RNBootSplash’s native pod only
links once CocoaPods has resolved it.
4. Hide it from JS once your app is ready
Section titled “4. Hide it from JS once your app is ready”With the native binary wired, hide() is the simple case — call it once your JS tree has
mounted:
import { useEffect } from 'react';import { hide } from '@symbiote-native/splash-screen/react';
useEffect(() => { hide();}, []);<script setup lang="ts">import { onMounted } from 'vue';import { hide } from '@symbiote-native/splash-screen/vue';
onMounted(() => hide());</script>Called once from the root component’s ngOnInit:
import { Component, OnInit } from '@angular/core';import { hide } from '@symbiote-native/splash-screen/angular';
@Component({ /* ... */ })export class App implements OnInit { ngOnInit(): void { hide(); }}<script lang="ts"> import { hide } from '@symbiote-native/splash-screen/svelte';
$effect(() => { hide(); });</script>If you want a fade transition gated on real readiness (layout committed + logo/brand images
loaded + your own ready flag) instead of an immediate cut, reach for useHideAnimation —
covered with full React/Vue/Angular examples on the
package page.
Known upstream gotchas
Section titled “Known upstream gotchas”These are react-native-bootsplash behaviors this wrapper inherits as-is — worth knowing before
you file a bug against @symbiote-native/splash-screen itself:
- Dark mode follows the OS appearance setting, not an in-app theme toggle.
darkBackground/darkLogoin the manifest are picked based ongetConstants().darkModeEnabled— the system dark-mode flag read before JS runs, at native paint time. If your app has its own theme switcher independent of the OS setting, the native splash can briefly flash the other color scheme for a frame before your JS-driven UI repaints (zoontek/react-native-bootsplash#743). There is no JS-side fix — it’s a property of native paint happening before any JS runs. - Android: relaunching via a notification can re-show, and occasionally get stuck on, the
boot theme, with
isVisible()reportingfalseandhide()having no visible effect in that stuck state — an open upstream edge case, not something this wrapper can route around (zoontek/react-native-bootsplash#736). If you see a splash reappear only on notification-triggered launches, this is why.
| Step | What | Where |
|---|---|---|
| 1 | Install the wrapper | package.json |
| 2 | Generate the binary (assets + native config) | npx symbiote-splash-screen generate |
| 3 | Wire the generated theme/storyboard into native entry points | MainActivity.kt, AppDelegate.swift, Info.plist |
| 4 | Hide it from JS | hide() / useHideAnimation in app code |
Steps 1–3 are native, one-time setup per app; step 4 is the only part that lives in your framework code, and it’s identical in shape across React, Vue, Angular, and Svelte.