Rtl
RTL (Right-to-Left) Layout in React Native feat. I18nManager
Our company recently decided to expand into Arabic-speaking markets. As part of this initiative, my coworker and I needed to research and implement RTL (Right-to-Left) support in our React Native app. This post explains the approaches we explored and how we ultimately implemented RTL support.
I18nManager
I18nManager
is a built-in utility in React Native that helps developers enable RTL layout behavior at the native level. It provides methods such as:
allowRTL()
forceRTL()
swapLeftAndRightInRTL()
- and a boolean property
isRTL
<- This is the most important
However, there’s a key caveat: changes to I18nManager
often require a full app restart to take effect. This is because React Native reads the RTL configuration from the native layer only once when the JavaScript runtime initializes.
So as a developer, you’re faced with a choice:
- Use
I18nManager
for native-level RTL support (requires restart), or - Manage RTL layout behavior entirely on the JS side using global state/context (no restart)
Without Using I18nManager
If you want to support RTL without restarting the app, you can detect the current language and conditionally style components. Here’s how:
Step 1: Determine RTL language
// utils/isRTL.ts
export const isRTL = (locale: string) =>
['ar', 'he', 'fa', 'ur'].includes(locale.split('-')[0]);
Step 2: Provide it via context (or state management)
// context/LocaleContext.tsx
import React from 'react';
export const LocaleContext = React.createContext({ isRTL: false });
Step 3: Use dynamic styles based on isRTL
// components/RtlCard.tsx
import React from 'react';
import { View, Text, StyleSheet, Image } from 'react-native';
import { useTranslation } from 'react-i18next';
import { isRTL } from '../utils/isRTL';
const RtlCard = () => {
const { i18n } = useTranslation();
const rtl = isRTL(i18n.language);
return (
<View style={[styles.card, rtl && styles.cardRTL]}>
<Image
source=
style={[styles.image, rtl && styles.imageRTL]}
/>
<View style={styles.textContainer}>
<Text style={[styles.title, rtl && styles.titleRTL]}>
{rtl ? 'مرحبا بك' : 'Welcome'}
</Text>
<Text style={[styles.description, rtl && styles.descriptionRTL]}>
{rtl
? 'هذا النص يتم عرضه من اليمين إلى اليسار.'
: 'This text is shown left to right.'}
</Text>
</View>
</View>
);
};
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
padding: 16,
backgroundColor: '#fff',
borderRadius: 12,
margin: 16,
alignItems: 'center',
},
cardRTL: {
flexDirection: 'row-reverse',
},
image: {
width: 50,
height: 50,
marginRight: 12,
},
imageRTL: {
marginRight: 0,
marginLeft: 12,
},
textContainer: {
flex: 1,
},
title: {
fontSize: 18,
textAlign: 'left',
},
titleRTL: {
textAlign: 'right',
},
description: {
fontSize: 14,
color: '#666',
textAlign: 'left',
},
descriptionRTL: {
textAlign: 'right',
},
});
Step 4: Update layout when language changes
To reflect layout changes dynamically, you can re-render the entire app using a key
prop based on language:
// App.tsx
const App = () => {
const { language } = useTranslation();
return <MainApp key={language} />;
};
✅ This method does not require restarting the app and is highly customizable.
🧰 Approach 2: Using I18nManager
(Native-level RTL)
If you don’t want to manually conditionally style every component, you can use I18nManager
for full RTL support from the native side.
However, there’s a caveat:
I18nManager
changes (like callingforceRTL(true)
) are only read during the initial launch of the JS runtime. They do not automatically trigger layout updates.
This means you must manually restart the app after toggling RTL using something like:
import { I18nManager } from 'react-native';
import RNRestart from 'react-native-restart';
I18nManager.allowRTL(true);
I18nManager.forceRTL(true);
RNRestart.Restart();
🔗 react-native-restart
is a helpful library for doing this gracefully.
🧾 Summary
Approach | Auto Layout Flip | Restart Required | Custom Styling |
---|---|---|---|
I18nManager | ✅ Yes | ⚠️ Yes | ❌ Minimal |
Manual (JS-only) | ❌ No (you control it) | ✅ No | ✅ Full control |
Choose based on your app’s scale, available time, and team resources. For our case, we chose manual RTL handling with global isRTL
logic because it gave us more flexibility and required no app restarts.