react-native-inappbrowser-nitro
v3.0.1
Published
react-native-inappbrowser-nitro is a react native package built with Nitro
Maintainers
Readme
react-native-inappbrowser-nitro
A modern, native in-app browser for React Native — built on Nitro Modules.
Installation · Quick start · API · Options · Migration · FAQ
Why this library?
| | |
|---|---|
| ⚡ Native speed | Direct JSI bindings via Nitro Modules — no JSON bridge, no scheduler hops. |
| 🎯 Right primitive on each platform | SFSafariViewController on iOS, Chrome Custom Tabs on Android — not a WKWebView reimplementation. |
| 🔐 OAuth-ready | First-class openAuth flow with ephemeral sessions and redirect interception. |
| 🪝 Hook + imperative APIs | useInAppBrowser() for component state, named exports for everything else. |
| 🧩 Strict TypeScript | Discriminated result types, dual as const + literal-type enums, full JSDoc with examples. |
| 📦 Small footprint | "sideEffects": false, ESM-first build, lazy-initialized native module. |
Requirements
| | Minimum | Tested up to |
|---|---|---|
| React Native | 0.75 (New Architecture) | 0.85 |
| iOS | 15.1 | 26.2 |
| Android | API 23 (Android 6) | API 36 (Android 16) |
| react-native-nitro-modules | 0.35 | 0.35.4 |
[!IMPORTANT] This library requires the React Native New Architecture and is not compatible with Expo Go. It works in Expo prebuild / dev clients.
Installation
yarn add react-native-inappbrowser-nitro react-native-nitro-modulesnpm 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-modulesiOS
cd ios && pod installAndroid
Autolinking handles everything. No manual MainApplication edits required.
Quick start
Imperative API
import { isAvailable, open } from 'react-native-inappbrowser-nitro'
if (await isAvailable()) {
const result = await open('https://github.com', {
preferredBarTintColor: { light: '#FFFFFF', dark: '#000000' }, // iOS
toolbarColor: { light: '#FFFFFF', dark: '#000000' }, // Android
readerMode: true,
})
if (result.type === 'success') {
console.log('Opened', result.url)
}
}React hook
import { useInAppBrowser } from 'react-native-inappbrowser-nitro/hooks'
function DocsButton() {
const { open, isLoading, error } = useInAppBrowser()
return (
<Pressable
disabled={isLoading}
onPress={() => open('https://nitro.margelo.com')}
>
<Text>{isLoading ? 'Opening…' : 'Open docs'}</Text>
{error && <Text style={{ color: 'red' }}>{error.message}</Text>}
</Pressable>
)
}The hook handles isLoading / error state, is safe to call after unmount (state updates are guarded), and returns stable open/openAuth references via useCallback so it's safe to put them in effect dependency arrays.
OAuth / SSO with openAuth
import { openAuth } from 'react-native-inappbrowser-nitro'
const result = await openAuth(
'https://example.com/oauth/authorize?client_id=…&redirect_uri=myapp%3A%2F%2Fcb',
'myapp://cb',
{
ephemeralWebSession: true, // iOS: don't share cookies with Safari
enableEdgeDismiss: false, // iOS: disable swipe-to-dismiss while authing
forceCloseOnRedirection: true, // Android: close tab once redirect is hit
}
)
if (result.type === 'success' && result.url) {
const code = new URL(result.url).searchParams.get('code')
// …exchange the code for a token
}API
All exports come from the package root unless noted. Every function returns a Promise.
| Export | Signature | Description |
|---|---|---|
| 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 that resolves the moment the native runtime 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 | () => UseInAppBrowserReturn | Hook wrapping open/openAuth with isLoading + error state. Exported from react-native-inappbrowser-nitro/hooks. |
Result shape
type BrowserResultType = 'cancel' | 'dismiss' | 'success'
interface InAppBrowserResult {
type: BrowserResultType
url?: string // final URL captured by the browser session
message?: string // human-readable reason on `dismiss`
}Errors
open and openAuth reject with an Error when:
- the URL is empty, missing a scheme, or
- the URL uses a denied scheme (
javascript:,data:,vbscript:).
These are sanity checks performed in JS before the call ever crosses JSI.
Options
open and openAuth accept a single options object that aggregates every iOS and Android knob. Cross-platform fields apply everywhere; @platform fields are silently ignored on the other platform.
iOS
| Option | Type | Default | Notes |
|---|---|---|---|
| dismissButtonStyle | 'done' \| 'close' \| 'cancel' | 'done' | Toolbar dismiss button label. |
| preferredBarTintColor | DynamicColor | system | Toolbar background. iOS 26 limitation: see iOS 26 Liquid Glass. |
| preferredControlTintColor | DynamicColor | system | Toolbar button tint. iOS 26 limitation: see below. |
| preferredStatusBarStyle | 'default' \| 'lightContent' \| 'darkContent' | system | Status bar appearance while presented. |
| readerMode | boolean | false | Open in Safari Reader Mode if the page supports it. |
| animated | boolean | true | Animate present/dismiss. |
| modalPresentationStyle | ModalPresentationStyle | 'automatic' | UIKit modal style. |
| modalTransitionStyle | ModalTransitionStyle | 'coverVertical' | UIKit transition (use 'partialCurl' only with 'fullScreen'). |
| modalEnabled | boolean | true | Present modally vs. push onto navigation stack. |
| enableBarCollapsing | boolean | false | Collapse toolbar on scroll. |
| ephemeralWebSession | boolean | false | openAuth only: don't persist cookies/credentials. |
| enableEdgeDismiss | boolean | true | Allow swipe-from-edge to dismiss. |
| overrideUserInterfaceStyle | 'unspecified' \| 'light' \| 'dark' | 'unspecified' | Force light/dark regardless of system theme. |
| formSheetPreferredContentSize | { width, height } | UIKit | Size when modalPresentationStyle: 'formSheet' (iPad). |
Android
| Option | Type | Default | Notes |
|---|---|---|---|
| showTitle | boolean | false | Show page title beneath URL bar. |
| toolbarColor | DynamicColor | browser default | Top toolbar background. |
| secondaryToolbarColor | DynamicColor | browser default | Bottom toolbar background. |
| navigationBarColor | DynamicColor | system | API 27+. |
| navigationBarDividerColor | DynamicColor | system | API 28+. |
| enableUrlBarHiding | boolean | false | Hide URL bar on scroll. |
| enableDefaultShare | boolean | false | Show 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. |
| headers | Record<string, string> | {} | HTTP headers on the initial request. |
| forceCloseOnRedirection | boolean | false | Auto-close tab when redirect URL matches (auth flows). |
| hasBackButton | boolean | false | Show back arrow instead of "X". |
| browserPackage | string | auto | Pin to a specific browser (e.g. 'com.android.chrome'). |
| showInRecents | boolean | true | Keep tab in Android Recents after closing. |
| includeReferrer | boolean | false | Send host app's package as Referrer. |
| instantAppsEnabled | boolean | true | Allow Instant Apps to handle the URL. |
| enablePullToRefresh | boolean | false | Enable swipe-to-refresh. |
| enablePartialCustomTab | boolean | false | Show as resizable bottom-sheet (Android 13+). |
| animations | BrowserAnimations | system | Custom enter/exit animation resource names. |
Dynamic colors
Color options accept a DynamicColor object that adapts to system appearance:
interface DynamicColor {
base?: string // fallback for any mode
light?: string // light mode override
dark?: string // dark mode override
highContrast?: string // applied when "Increase Contrast" is enabled (iOS 26+, Android 16+)
}Each value is a #RRGGBB or #AARRGGBB hex string. If a mode-specific value is missing, the platform falls back to base, then to the system default.
Platform notes
iOS 26 Liquid Glass
iOS 26 redesigned SFSafariViewController around the system Liquid Glass material. The toolbar is now a translucent surface that samples content beneath it, so:
preferredBarTintColorhas little to no visible effect on iOS 26.preferredControlTintColoris partially overridden by the system's adaptive monochrome treatment — custom tints may render with lower contrast.
This is a platform behavior change that affects every wrapper around SFSafariViewController. There is no public API to opt out of the glass material. The properties are still forwarded for iOS ≤ 18 compatibility.
If pixel-exact branding of the chrome is critical, consider a WKWebView-based component for non-auth flows. Do not use WKWebView for OAuth — it does not share Safari's process isolation, cookies, or autofill, and many providers explicitly forbid it.
Android browser fallback
Android prefers Chrome Custom Tabs when available. On devices without a Custom Tabs–capable browser the system surfaces a chooser via Intent.ACTION_VIEW, and option fields like toolbarColor are silently ignored.
Migrating from react-native-inappbrowser-reborn
- import InAppBrowser from 'react-native-inappbrowser-reborn'
+ import { open, openAuth, close, isAvailable } from 'react-native-inappbrowser-nitro'
- await InAppBrowser.open(url, { toolbarColor: '#6200EE' })
+ await open(url, { toolbarColor: { base: '#6200EE', dark: '#3700B3' } })
- await InAppBrowser.openAuth(url, redirectUrl, { ephemeralWebSession: true })
+ await openAuth(url, redirectUrl, { ephemeralWebSession: true })Key differences:
- Named exports instead of a default class — better tree-shaking.
DynamicColorobjects instead of single hex strings, so the same call adapts to light/dark/high-contrast.- String-literal enums (
'success','cancel','dismiss') forresult.type— fully type-narrowable. - Hook-based state:
useInAppBrowser()exposesisLoadinganderrorwithout manual wrappers. - No bridge: every call goes through Nitro's JSI hybrid object.
FAQ
SFSafariViewController and Chrome Custom Tabs share the system Safari/Chrome session — including cookies, autofill, content blockers, and (critically) password autofill from iCloud Keychain / Google Password Manager. They also run in a separate process from your app, so the host app cannot read page content. This is exactly what most OAuth providers require. A WKWebView cannot offer any of that.
Yes — in Expo prebuild / dev client projects. It does not work in Expo Go (managed workflow) because Nitro requires native compilation.
No. Nitro Modules require the New Architecture (newArchEnabled=true on Android, Fabric/TurboModule autolinking on iOS).
The default Android emulator image often ships without a Custom Tabs–capable browser. Install Chrome from the Play Store image, or use a Pixel system image with Play Services preinstalled.
You're probably calling open instead of openAuth. openAuth uses ASWebAuthenticationSession, which is the only iOS API allowed to intercept a redirect URL programmatically. open uses SFSafariViewController, which can't do that.
Most often this means the URL was rejected by the JS-side validator (empty / missing scheme / denied scheme). Check result.message for the reason. Logs from the native side are also visible in Xcode / Logcat.
Contributing
Contributions are very welcome. The library is small and well-tested — a great place to land your first React Native PR.
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 lintRun the example app:
cd example
yarn ios # or: yarn androidA 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).
Acknowledgements
- Marc Rousavy and the Margelo team for Nitro Modules.
react-native-inappbrowser-reborn— the original API inspiration.
