Skip to content

Repository files navigation

react-native-inappbrowser-nitro

Native in-app browser for React Native, powered by Nitro Modules.

npm versionnpm downloadsbundle sizelicenseCICI

Installation · Quick start · API · Options · FAQ · Changelog

Demo

⚡ Why this library?

JSI bindingsDirect native calls through Nitro Modules. No JSON serialization, no scheduler hops.
🎯 Right primitive per platformSFSafariViewController on iOS, Chrome Custom Tabs on Android. Not a WKWebView reimplementation.
🔐 OAuth built inopenAuth wraps ASWebAuthenticationSession with ephemeral sessions and redirect interception.
🪝 Hook + functionsuseInAppBrowser() for React state, named exports for everything else.
🧩 TypeScript firstDiscriminated result types, as const enums, full JSDoc.
📦 Tree-shakeable"sideEffects": false, ESM build, lazy native module init.

📋 Requirements

MinimumTested up to
React Native0.75 (New Architecture)0.85
iOS15.126.2
AndroidAPI 23 (Android 6)API 36 (Android 16)
react-native-nitro-modules0.350.35.4

Important

This library requires the React Native New Architecture and does not work in Expo Go. Use Expo prebuild / dev clients instead.


📦 Installation

yarn add react-native-inappbrowser-nitro react-native-nitro-modules
npm / pnpm / bun
npm install react-native-inappbrowser-nitro react-native-nitro-modules
pnpm add react-native-inappbrowser-nitro react-native-nitro-modules
bun add react-native-inappbrowser-nitro react-native-nitro-modules

iOS

cd ios && pod install

Android

Autolinking handles everything. No manual MainApplication edits.

Note

Release builds with ProGuard/R8 need this rule in android/app/proguard-rules.pro:

# react-native-inappbrowser-nitro
-keep class com.inappbrowsernitro.** { *; }

Without it, you'll see Couldn't find class 'com/inappbrowsernitro/HybridInappbrowserNitro'.


🚀 Quick start

Hook

import{useInAppBrowser}from'react-native-inappbrowser-nitro/hooks'functionDocsButton(){const{ open, isLoading, error }=useInAppBrowser()return(<Pressabledisabled={isLoading}onPress={()=>open('https://nitro.margelo.com')}><Text>{isLoading ? 'Opening…' : 'Open docs'}</Text>{error&&<Textstyle={{color: 'red'}}>{error.message}</Text>}</Pressable>)}

The hook guards state updates after unmount and returns stable open/openAuth references via useCallback.

Imperative

import{isAvailable,open}from'react-native-inappbrowser-nitro'if(awaitisAvailable()){constresult=awaitopen('https://github.com',{preferredBarTintColor: {light: '#FFFFFF',dark: '#000000'},// iOStoolbarColor: {light: '#FFFFFF',dark: '#000000'},// AndroidreaderMode: true,})if(result.type==='success'){console.log('Opened',result.url)}}

OAuth / SSO

import{openAuth}from'react-native-inappbrowser-nitro'constresult=awaitopenAuth('https://example.com/oauth/authorize?client_id=…&redirect_uri=myapp%3A%2F%2Fcb','myapp://cb',{ephemeralWebSession: true,// iOS: don't share Safari cookiesenableEdgeDismiss: false,// iOS: block swipe-to-dismiss during authforceCloseOnRedirection: true,// Android: close tab on redirect match})if(result.type==='success'&&result.url){constcode=newURL(result.url).searchParams.get('code')// exchange code for token}

📖 API

All exports come from the package root unless noted. Every function returns a Promise.

ExportSignatureDescription
isAvailable() => Promise<boolean>true when a compliant Safari/Custom Tabs runtime is reachable. Always true on iOS; on Android requires a Custom Tabs–capable browser.
open(url, options?) => Promise<InAppBrowserResult>Present an in-app browser. Resolves when the user dismisses or the system closes it.
openAuth(url, redirectUrl, options?) => Promise<InAppBrowserAuthResult>Run an authentication session. Resolves the moment native code intercepts a navigation matching redirectUrl.
close() => Promise<void>Dismiss the current browser. No-op when none is presented.
closeAuth() => Promise<void>Cancel an in-flight openAuth session.
useInAppBrowser() => UseInAppBrowserReturnHook wrapping open/openAuth with isLoading + error state. Exported from react-native-inappbrowser-nitro/hooks.

Result shape

typeBrowserResultType='cancel'|'dismiss'|'success'interfaceInAppBrowserResult{type: BrowserResultTypeurl?: string// final URL captured by the browser sessionmessage?: string// human-readable reason on `dismiss`}

Errors

open and openAuth reject with an Error when the URL is empty, missing a scheme, or uses a denied scheme (javascript:, data:, vbscript:). These checks run in JS before the call crosses JSI.


⚙️ Options

open and openAuth accept one options object. Platform-only fields are ignored on the other platform.

iOS

OptionTypeDefaultNotes
dismissButtonStyle'done' | 'close' | 'cancel''done'Toolbar dismiss button label.
preferredBarTintColorDynamicColorsystemSafari toolbar background hint. iOS 26 Liquid Glass may ignore it.
preferredControlTintColorDynamicColorsystemSafari control tint hint. iOS 26 may adapt it for contrast.
preferredStatusBarStyle'default' | 'lightContent' | 'darkContent'systemStatus bar appearance while presented.
readerModebooleanfalseiOS only. Ask Safari to enter Reader Mode if the page supports it; Android Custom Tabs ignore this option.
animatedbooleantrueAnimate present/dismiss.
modalPresentationStyleModalPresentationStyle'automatic'UIKit modal style.
modalTransitionStyleModalTransitionStyle'coverVertical'UIKit transition. Use 'partialCurl' only with 'fullScreen'.
modalEnabledbooleantruePresent modally instead of pushing onto a navigation stack.
enableBarCollapsingbooleanfalseCollapse toolbar on scroll.
ephemeralWebSessionbooleanfalseopenAuth only: do not persist cookies/credentials.
enableEdgeDismissbooleantrueAllow swipe-from-edge to dismiss.
overrideUserInterfaceStyle'unspecified' | 'light' | 'dark''unspecified'Force light/dark while presented.
formSheetPreferredContentSize{ width, height }UIKitPreferred form-sheet size. UIKit may adapt or ignore it on iPhone.

Android

OptionTypeDefaultNotes
showTitlebooleanfalseShow page title under the URL bar.
toolbarColorDynamicColorbrowser defaultTop toolbar background.
secondaryToolbarColorDynamicColorbrowser defaultBottom toolbar background.
navigationBarColorDynamicColorsystemAPI 27+.
navigationBarDividerColorDynamicColorsystemAPI 28+.
enableUrlBarHidingbooleanfalseHide URL bar on scroll.
enableDefaultSharebooleanfalseShow share menu item. Use shareState for finer control.
shareState'default' | 'on' | 'off''default'Override share menu visibility.
colorScheme'system' | 'light' | 'dark''system'Custom Tab theme hint.
headersRecord<string, string>{}HTTP headers on initial request.
forceCloseOnRedirectionbooleanfalseAuto-close tab when redirect URL matches.
hasBackButtonbooleanfalseShow back arrow instead of X.
browserPackagestringautoPin to a specific browser, e.g. com.android.chrome.
showInRecentsbooleantrueKeep the tab in Android Recents after closing.
includeReferrerbooleanfalseSend the host app package as Referrer.
instantAppsEnabledbooleantrueAllow Instant Apps to handle the URL.
enablePullToRefreshbooleanfalseEnable swipe-to-refresh.
enablePartialCustomTabbooleanfalseShow a resizable bottom sheet on Android 13+.
animationsBrowserAnimationssystemCustom enter/exit animation resource names.

Dynamic colors

Color options accept a DynamicColor object:

interfaceDynamicColor{base?: string// fallbacklight?: string// light modedark?: string// dark modehighContrast?: string// increased contrast, where supported}

Each value must be #RRGGBB or #AARRGGBB. Missing mode-specific values fall back to base, then the system default.


🔧 Platform notes

iOS 26 Liquid Glass

iOS 26 renders SFSafariViewController chrome with system Liquid Glass. Apple controls the final toolbar material, contrast, and legibility:

  • preferredBarTintColor can have little or no visible effect.
  • preferredControlTintColor may be adapted by the system.
  • formSheetPreferredContentSize is only a UIKit preference and is commonly adapted on iPhone.

The properties are still forwarded for iOS versions and contexts that honor them.

If pixel-exact browser chrome matters, use a WKWebView-based screen for non-auth flows. Do not use WKWebView for OAuth; it lacks Safari's process isolation, cookie sharing, autofill, and many providers forbid it.

Android browser fallback

Android prefers Chrome Custom Tabs. On devices without a Custom Tabs–capable browser the system shows a chooser via Intent.ACTION_VIEW, and option fields like toolbarColor are silently ignored.


❓ FAQ

Why not use WKWebView / react-native-webview?

SFSafariViewController and Chrome Custom Tabs share the system Safari/Chrome session — cookies, autofill, content blockers, and password autofill from iCloud Keychain / Google Password Manager. They run in a separate process from your app, so the host app cannot read page content. Most OAuth providers require this. WKWebView offers none of it.

Does it work with Expo?

Yes, in Expo prebuild / dev client projects. It does not work in Expo Go (managed workflow) because Nitro requires native compilation.

Can I use this with the Old Architecture?

No. Nitro Modules require the New Architecture (newArchEnabled=true on Android, Fabric/TurboModule autolinking on iOS).

"InAppBrowser is not available" on Android emulator

The default emulator image ships without a Custom Tabs–capable browser. Install Chrome from the Play Store image, or use a Pixel system image with Play Services preinstalled.

Why does my OAuth flow open in Safari instead of in-app?

You're calling open instead of openAuth. openAuth uses ASWebAuthenticationSession, the only iOS API that can intercept a redirect URL programmatically. open uses SFSafariViewController, which cannot.

Result type is 'dismiss' right after I call open

The URL was rejected by the JS-side validator (empty / missing scheme / denied scheme). Check result.message for the reason. Native-side logs are also visible in Xcode / Logcat.


🤝 Contributing

Contributions welcome. The library is small and well-tested — a good place to land your first React Native PR.

Found a bug or have a feature request? Open an issue.

git clone https://github.com/mCodex/react-native-inappbrowser-nitro
cd react-native-inappbrowser-nitro
yarn install
yarn codegen # regenerate Nitro bindings + build
yarn typecheck
yarn lint

Run the example app:

cd example
yarn ios # or: yarn android

A pre-commit hook (Husky + lint-staged + Biome) auto-formats staged files. CI runs on iOS (macos-26, Xcode 26.2) and Android (ubuntu-latest, JDK 21).


📄 License

MIT © Mateus Andrade

About

🚀 Lightning-fast in-app browser for React Native powered by Nitro Modules. Direct JSI bindings for native performance with Safari View Controller (iOS) & Chrome Custom Tabs (Android). Zero bridge overhead, TypeScript-first, with React hooks support.

Topics

Resources

Stars

45 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Contributors

Languages