react-native-inappbrowser-nitro
v3.0.2
Published
react-native-inappbrowser-nitro is a react native package built with Nitro
Downloads
9,843
Maintainers
Readme
react-native-inappbrowser-nitro
Native in-app browser for React Native, powered by Nitro Modules.
Installation · Quick start · API · Options · FAQ · Changelog
⚡ Why this library?
| | |
|---|---|
| ⚡ JSI bindings | Direct native calls through Nitro Modules. No JSON serialization, no scheduler hops. |
| 🎯 Right primitive per platform | SFSafariViewController on iOS, Chrome Custom Tabs on Android. Not a WKWebView reimplementation. |
| 🔐 OAuth built in | openAuth wraps ASWebAuthenticationSession with ephemeral sessions and redirect interception. |
| 🪝 Hook + functions | useInAppBrowser() for React state, named exports for everything else. |
| 🧩 TypeScript first | Discriminated result types, as const enums, full JSDoc. |
| 📦 Tree-shakeable | "sideEffects": false, ESM build, lazy native module init. |
📋 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 does not work in Expo Go. Use Expo prebuild / dev clients instead.
📦 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.
[!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'
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 guards state updates after unmount and returns stable open/openAuth references via useCallback.
Imperative
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)
}
}OAuth / SSO
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 Safari cookies
enableEdgeDismiss: false, // iOS: block swipe-to-dismiss during auth
forceCloseOnRedirection: true, // Android: close tab on redirect match
}
)
if (result.type === 'success' && result.url) {
const code = new URL(result.url).searchParams.get('code')
// exchange code for 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. 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 | () => 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 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
| Option | Type | Default | Notes |
|---|---|---|---|
| dismissButtonStyle | 'done' \| 'close' \| 'cancel' | 'done' | Toolbar dismiss button label. |
| preferredBarTintColor | DynamicColor | system | Safari toolbar background hint. iOS 26 Liquid Glass may ignore it. |
| preferredControlTintColor | DynamicColor | system | Safari control tint hint. iOS 26 may adapt it for contrast. |
| preferredStatusBarStyle | 'default' \| 'lightContent' \| 'darkContent' | system | Status bar appearance while presented. |
| readerMode | boolean | false | iOS only. Ask Safari to enter Reader Mode if the page supports it; Android Custom Tabs ignore this option. |
| 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 instead of pushing onto a navigation stack. |
| enableBarCollapsing | boolean | false | Collapse toolbar on scroll. |
| ephemeralWebSession | boolean | false | openAuth only: do not persist cookies/credentials. |
| enableEdgeDismiss | boolean | true | Allow swipe-from-edge to dismiss. |
| overrideUserInterfaceStyle | 'unspecified' \| 'light' \| 'dark' | 'unspecified' | Force light/dark while presented. |
| formSheetPreferredContentSize | { width, height } | UIKit | Preferred form-sheet size. UIKit may adapt or ignore it on iPhone. |
Android
| Option | Type | Default | Notes |
|---|---|---|---|
| showTitle | boolean | false | Show page title under the 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 initial request. |
| forceCloseOnRedirection | boolean | false | Auto-close tab when redirect URL matches. |
| 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 the tab in Android Recents after closing. |
| includeReferrer | boolean | false | Send the host app package as Referrer. |
| instantAppsEnabled | boolean | true | Allow Instant Apps to handle the URL. |
| enablePullToRefresh | boolean | false | Enable swipe-to-refresh. |
| enablePartialCustomTab | boolean | false | Show a resizable bottom sheet on Android 13+. |
| animations | BrowserAnimations | system | Custom enter/exit animation resource names. |
Dynamic colors
Color options accept a DynamicColor object:
interface DynamicColor {
base?: string // fallback
light?: string // light mode
dark?: string // dark mode
highContrast?: 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:
preferredBarTintColorcan have little or no visible effect.preferredControlTintColormay be adapted by the system.formSheetPreferredContentSizeis 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
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.
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 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.
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.
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 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).
