Native animated splash screens for React Native — not a static picture.
- Android 12+: drives the official SplashScreen API with an animated vector drawable icon (the Gmail-style launch), then slides the whole splash up off-screen to reveal your app already rendered underneath.
- iOS: shows a SwiftUI animation in its own window above the app — ship a built-in animation or inject any SwiftUI view of your own; in Expo projects your Swift file lives in
assets/and the plugin injects it on every prebuild, soprebuild --cleannever eats it. - JS: one call,
SplashFlow.hide(), whose promise resolves only after the exit animation has fully finished, so you can sequence status-bar changes or entrance animations without racing the splash. - Pluggable exit: pick a built-in exit animation (
slide-up,slide-down,fade,zoom-out,none) or supply a fully custom one in native code on either platform. - Expo: a config plugin wires up everything (
MainActivity, themes, drawables,AppDelegate, asset catalog) onexpo prebuild. - New-architecture TurboModule. No artificial delay: launch feels exactly as fast as your app actually is (a minimum display duration is opt-in).
| Android | iOS | |
|---|---|---|
| Splash rendering | System splash screen (API 31+) with your animated vector drawable icon, themed background color | SwiftUI view hosted in a dedicated UIWindow above the main window |
| Waiting | setKeepOnScreenCondition holds the system splash until JS calls hide() |
The overlay window stays up until JS calls hide() |
| Exit | Splash view slides up over 450 ms with a sheet-dismissal cubic curve | Splash window slides up over 500 ms with the same curve, with a soft drop shadow for depth |
| Below Android 12 | Static splash: themed window background (no exit animation) | — |
hide() promise |
Resolves after the exit animation and view removal (with a watchdog for OEM quirks) | Resolves after the exit animation and window teardown |
Deep dive into every file, the full launch-to-reveal lifecycle, and the design decisions: docs/architecture.md.
npm install react-native-splash-flow
# or
yarn add react-native-splash-flowNew architecture only (React Native ≥ 0.75, the default since 0.76).
Requires a development build (
expo-dev-client/expo run:*). It cannot work in Expo Go — it is a native module.
- Add the plugin to
app.json/app.config.js:
{
"expo": {
"plugins": [
[
"react-native-splash-flow",
{
"android": {
"icon": "./assets/splashflow-icon.xml",
"backgroundColor": "#FFFFFF",
"backgroundColorDark": "#000000",
"animationDuration": 1000
},
"ios": {
"customView": "./assets/MySplashView.swift"
}
}
]
]
}
}-
If your app config still has the top-level
"splash"field (or theexpo-splash-screenplugin), remove it — two splash systems would fight over the launch theme. See docs/expo-plugin.md. -
Regenerate the native projects and run:
npx expo prebuild --clean
npx expo run:android # or run:ios- Hide the splash when your app is ready:
import SplashFlow from 'react-native-splash-flow';
useEffect(() => {
bootstrap().finally(async () => {
await SplashFlow.hide(); // resolves after the exit animation
});
}, []);All plugin options are documented in docs/expo-plugin.md.
The plugin only automates the steps below — doing them by hand works in any RN ≥ 0.75 app.
- MainActivity — install the splash before
super.onCreate()and before anysetTheme()call:
import com.splashflow.SplashFlowManager
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
SplashFlowManager.install(this)
// ... setTheme(R.style.AppTheme) if you have it ...
super.onCreate(null)
}
}- Theme — create
res/values-v31/splashflow.xml(and avalues-night-v31variant if you support dark mode):
<resources>
<style name="Theme.SplashFlow" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="android:windowSplashScreenBackground">@color/splashflow_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splashflow_icon</item>
<item name="android:windowSplashScreenAnimationDuration">1000</item>
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>Plus a base res/values/splashflow.xml fallback for Android < 12 (plain android:windowBackground), and the splashflow_background color in your colors resources.
- Manifest — point the launch activity at the theme:
<activity android:name=".MainActivity" android:theme="@style/Theme.SplashFlow" ...>- Icon — drop your animated vector drawable at
res/drawable/splashflow_icon.xml. Authoring guide: docs/android-avd.md.
In AppDelegate.swift, right after the main window is created:
import SplashFlow
import SwiftUI
// inside application(_:didFinishLaunchingWithOptions:)
SplashFlowManager.show(in: window) // classic animation, logo image named "Icon"
SplashFlowManager.show(in: window, logoImageName: "SplashLogo") // classic animation, your logo
SplashFlowManager.show(in: window, view: AnyView(EnergySplashFlowView())) // built-in code-drawn bolt animation
SplashFlowManager.show(in: window, view: AnyView(MySplashView())) // your own SwiftUI viewThe full custom-view guide is in docs/ios-custom-view.md.
Default exit is a slide-up reveal on both platforms. Change it:
Expo — plugin props: "android": { "exitAnimation": "fade" }, "ios": { "exitAnimation": "zoom-out" }.
Android (native) — next to install():
SplashFlowManager.setExitStyle(SplashFlowManager.ExitStyle.FADE)
// or fully custom:
SplashFlowManager.setExitAnimator(object : SplashFlowManager.ExitAnimator {
override fun getDurationMs() = 600L // feeds the stuck-animation watchdog
override fun animate(splashView: View, onComplete: Runnable) {
splashView.animate().alpha(0f).scaleX(1.3f).scaleY(1.3f)
.setDuration(600).withEndAction(onComplete).start()
}
})iOS (native) — before hide() is called (e.g. next to show(in:)):
SplashFlowManager.exitStyle = .fade
// or fully custom — call the completion exactly once when done:
SplashFlowManager.customExitAnimation = { window, complete in
UIView.animate(withDuration: 0.6, animations: {
window.alpha = 0
window.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
}) { _ in complete() }
}Available styles on both platforms: slide-up (default), slide-down, fade, zoom-out, none (instant).
import SplashFlow from 'react-native-splash-flow';Hides the splash. The promise resolves after the exit animation has completely finished and the splash view/window is gone. Idempotent — concurrent and repeated callers share a single native call, so calling it from several places is safe.
true once the configured minimum display duration has elapsed (always true with the default duration of 0).
On web or when the native module is missing, both calls resolve immediately (hide() with an informational string, isReady() with true).
| Member | Purpose |
|---|---|
install(Activity) |
Call at the very top of MainActivity.onCreate(), before super.onCreate() |
setMinimumDuration(long ms) |
Optional minimum splash duration (default 0) |
setExitStyle(ExitStyle) |
SLIDE_UP (default), SLIDE_DOWN, FADE, ZOOM_OUT, NONE |
setExitAnimator(ExitAnimator) |
Fully custom exit animation (overrides the style) |
hide() / hide(Runnable) |
Usually called via JS; the callback fires after the exit animation |
canHide() |
Whether the minimum duration has elapsed |
| Member | Purpose |
|---|---|
show(in:) |
Classic animation (logo image named "Icon", falling back to the app icon) |
show(in:logoImageName:accentColor:secondaryAccentColor:) |
Classic animation, custom logo/colors |
show(in:view:) |
Any AnyView you provide — full control (EnergySplashFlowView() is a built-in option) |
minimumDuration |
Optional minimum splash duration in seconds (default 0), set before show |
exitStyle |
.slideUp (default), .slideDown, .fade, .zoomOut, .none |
customExitAnimation |
(UIWindow, @escaping () -> Void) -> Void — fully custom exit |
hide(completion:) / canHide() |
Usually called via JS |
- Android < 12 shows a static splash — expected: the animated icon is an Android 12+ system feature; older versions get the themed background color and no exit animation.
- The animated icon plays only partially — the system caps
windowSplashScreenAnimationDurationat 1000 ms; design your AVD to land its pose within a second. - Expo: white flash or two splashes — remove the
"splash"field /expo-splash-screenplugin from your app config so only SplashFlow controls the launch theme. hide()resolves but you never saw the animation — the splash shows only on a cold launch; fully terminate the app and relaunch.- iOS shows the LaunchScreen storyboard for an instant first — normal: the storyboard is what iOS renders before your process runs; keep its background color aligned with your splash view so the handoff is invisible.
example/ is an Expo app (dev client) with the plugin configured, a sample animated vector drawable, and a demo screen that simulates startup work before calling hide(). From the repo root:
yarn
yarn example android # or iosSee the contributing guide to learn how to contribute to the repository and the development workflow.
MIT
Made with create-react-native-library