Skip to content

mCodex/react-native-inappbrowser-nitro

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

85 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

react-native-inappbrowser-nitro

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

npm version npm downloads bundle size license CI CI

Installation ยท Quick start ยท API ยท Options ยท FAQ ยท Changelog

Demo

โšก 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-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'

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:

  • 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

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors