npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-nitro-direction

v0.1.3

Published

Flip a React Native app between LTR and RTL at runtime — no restart, no reload, no remount. Native iOS + Android, built on Nitro Modules.

Readme

react-native-nitro-direction

npm version license

Flip your React Native app between LTR and RTL at runtime — no restart, no bundle reload, no remount. One call re-mirrors the React Native content and generic native view hierarchy in place, keeping navigation state, scroll position, and form state intact.

Built on Nitro Modules. Native owns the direction, JS reads and sets it. Same pattern as Appearance, Dimensions, Keyboard. Three exports — setDirection, getDirection, onDirectionChanged.

I18nManager.forceRTL(true) needs an app restart on the New Architecture. This package triggers React Native's own cold-start re-layout paths on every live Fabric surface instead.

  • Runtime flip — LTR ⇄ RTL without restarting
  • JSI-direct — no bridge serialization, native callbacks
  • Fabric & bridgeless — iOS + Android, New Architecture only
  • Autolinked — zero MainApplication / Podfile edits
  • Three exportssetDirection, getDirection, onDirectionChanged

Requirements

  • React Native New Architecture (Fabric) — default on RN 0.76+ / Expo SDK 52+
  • react-native-nitro-modules peer dependency
  • A bare app (RN CLI) or development build (Expo). Native code does not run in Expo Go
  • iOS 13+ / Android API 23+. The package's manifest merges android:supportsRtl="true" for you

Tested against: React Native 0.86 · react-native-nitro-modules 0.36.x

⚠️ The flip depends on non-contractual React Native internals — see How it works. Re-verify on major RN upgrades.


Installation

bun add react-native-nitro-direction react-native-nitro-modules

Rebuild the native app — the hybrid object must compile into the binary:

cd ios && pod install && cd ..
npx react-native run-ios
npx react-native run-android

Autolinks on both platforms. No manual native steps.


Quick start

import {
  setDirection,
  getDirection,
  onDirectionChanged,
} from 'react-native-nitro-direction'

// Flip the entire app
await setDirection('rtl')
await setDirection('ltr')

// Read the current direction
getDirection() // 'ltr' | 'rtl'

// Subscribe to native direction changes
const cleanup = onDirectionChanged((direction) => {
  // fires after the visual flip has landed on the main thread
})

getDirection() is the authoritative synchronous JS read. Use onDirectionChanged to keep React state and navigation direction contexts in sync. React Native's I18nManager.isRTL is only reactive when the host app includes a compatible React Native event patch.


API

| Export | Signature | Description | | --- | --- | --- | | setDirection | (direction: 'ltr' \| 'rtl') => Promise<'ltr' \| 'rtl'> | Flip the app's layout direction at runtime. Resolves once the native re-layout has landed. | | getDirection | () => 'ltr' \| 'rtl' | Get the current layout direction. | | onDirectionChanged | (callback: (dir: 'ltr' \| 'rtl') => void) => () => void | Subscribe to direction changes. Returns a cleanup function. | | useDirection | () => 'ltr' \| 'rtl' | React hook returning the current direction. Shares one module-level store across every caller — no Context provider required. |


Integration guide

Cold start — set direction before the first paint

Do this before rendering your navigator so the stored language is already correct on launch:

// app root, before <NavigationContainer> or the first <Stack>
const direction = getStoredDirection() // your logic — persisted pref, device locale, etc.
await setDirection(direction)

Language switch — direction first, then strings

Call setDirection before i18n.changeLanguage(). The tree must be RTL when the new strings measure; otherwise the text bakes its alignment while the tree is still LTR.

async function switchLanguage(locale: string) {
  const direction = isRTLLanguage(locale) ? 'rtl' : 'ltr' // your logic
  await setDirection(direction)        // 1. flip direction (native, no restart)
  await i18n.changeLanguage(locale)    // 2. swap the strings
}

Text alignment

React Native only mirrors <Text> / <TextInput> when their style includes textAlign. Use textAlign: 'left' — it's start-relative, so RN keeps it left in LTR and flips it to right under RTL. Don't hardcode a mirrored value; the native flip already does the swap, so double-mirroring cancels out.

const styles = StyleSheet.create({
  title: { textAlign: 'left' },  // ✅ flips with direction
  body:  { /* no textAlign */ }, // ❌ stays left under RTL
})

flexDirection: 'row' reverses automatically — no changes needed.

React Navigation / Expo Router

The native-stack push/pop animation and swipe-back gesture read their direction from LocaleDirContext, which defaults to the cached startup value and never re-reads at runtime. Keep this context synchronized with the package:

import { getDirection, onDirectionChanged } from 'react-native-nitro-direction'
import { LocaleDirContext } from 'expo-router/react-navigation'
import { useEffect, useState, type ReactNode } from 'react'

function DirectionProvider({ children }: { children: ReactNode }) {
  const [direction, setDirection] = useState(getDirection)
  useEffect(() => onDirectionChanged(setDirection), [])

  return (
    <LocaleDirContext.Provider value={direction}>
      {children}
    </LocaleDirContext.Provider>
  )
}

Subscribe to direction changes

useEffect(() => {
  return onDirectionChanged((direction) => {
    // recompute styles, invalidate caches, drive a styling engine plugin
  })
}, [])

The callback fires after the visual flip has landed on the main/UI thread, so styling engines can safely recompute.

The package does not own navigation controllers, headers, tab bars, or gestures. Navigation libraries should refresh their own chrome from this callback. The repository's example2 shows the small version-matched react-native-screens chevron patch used alongside LocaleDirContext.


How it works

setDirection(direction) sets RN's direction flags synchronously (RCTI18nUtil on iOS, I18nUtil SharedPreferences on Android) — so getDirection() is coherent immediately. Then it dispatches the view work to the main/UI thread in a single pass:

iOS

  1. Update RN's direction flags, then bust Yoga's size-keyed layout cache — bump each surface's min/max size by 1pt and immediately restore it, forcing a real re-layout in the new direction
  2. Mirror the generic window/view hierarchy. Navigation libraries retain ownership of their navigation controllers, headers, tab bars, and gestures through their own direction context.

Android

  1. forceLayout() + requestLayout() on every ReactSurfaceViewonMeasureupdateLayoutSpecsconstraintLayout, re-reading isRTL
  2. Set the decor view's layoutDirection so inheriting views (toolbars, headers) re-resolve

The key insight: Fabric normally reads the layout direction once at surface creation. Calling forceRTL updates the flag but doesn't re-mirror anything. This package triggers RN's own cold-start re-layout path on the live surface, which is why the flip works without a restart.

Non-contractual internals

The mechanism depends on React Native internals not covered by the public API:

  • Yoga keying its per-node layout cache on size + measure mode, not direction — hence the size-nudge (iOS)
  • ReactSurfaceView.onMeasure reading I18nUtil.isRTL fresh on every measure pass (Android)

If a future RN version changes these, the live re-layout path may need a version-specific update. The iOS bridge uses the public surface sizing methods available on RCTFabricSurface and falls back to a normal layout pass when a surface is not a Fabric surface.

When upgrading React Native: run the example app and flip every screen.


Troubleshooting

| Symptom | Likely cause | | --- | --- | | Nothing happens | Native module not in the binary — rebuild (pod install + run). Confirm react-native-nitro-modules is installed. | | Only flips after restart | You're on New Architecture? Confirm newArchEnabled. Are you await-ing setDirection()? | | Text doesn't right-align | Add textAlign: 'left' to that style. Unset alignment never flips. | | Text alignment is stale after language switch | Call setDirection before i18n.changeLanguage. | | Push animation / swipe-back stuck in old direction | Override LocaleDirContext (see React Navigation section). |

License

MIT