Shipping Arabic-First React Native Apps: RTL Lessons from Two Production Launches
What I learned building Harf Media and Basra TV — two Arabic-first React Native apps — about RTL layouts, fonts, numerals, and the bugs that only show up in right-to-left.
Over the past two years I shipped two Arabic-first React Native apps to the App Store and Google Play: Harf Media, an out-of-home advertising platform, and Basra TV, a digital newspaper with live broadcasting. Both serve Iraqi users, both are right-to-left from the first screen, and both taught me that RTL is not a checkbox you tick at the end of a project.
Here is what I would tell myself before starting the first one.
Decide RTL-first or RTL-also on day one
There are two very different projects hiding behind "the app needs Arabic support":
- RTL-also: an LTR app that can flip when the user switches language.
- RTL-first: Arabic is the primary (or only) language, and LTR is secondary or absent.
Both of my apps were RTL-first, which simplifies some things — you can force the direction once at startup and stop worrying:
import { I18nManager } from "react-native"
I18nManager.allowRTL(true)
I18nManager.forceRTL(true)
The catch nobody tells you: forceRTL does not take effect until the app restarts. During development this cost me a confused afternoon — the setting is persisted natively and applied on the next launch, not the current one. If you support language switching at runtime, you have to prompt the user to restart (or restart programmatically), and you need to test the "first launch after switching" path explicitly.
Stop writing left and right
The single highest-leverage habit: never use physical direction properties in styles. React Native has supported logical properties for years, and they flip automatically under RTL:
// Bad — breaks the moment the layout flips
{ marginLeft: 16, paddingRight: 8, left: 0 }
// Good — flips with the layout direction
{ marginStart: 16, paddingEnd: 8, start: 0 }
flexDirection: "row" already reverses automatically in RTL, which handles most layouts for free. The bugs live in the places where someone hard-coded a physical side: absolutely positioned badges, swipe gesture thresholds, animation translateX values, and carousel scroll offsets. Every one of those needs to be direction-aware:
const dir = I18nManager.isRTL ? -1 : 1
translateX.value = withTiming(dir * OFFSET)
Icons: some flip, some must not
Directional icons — back chevrons, "next" arrows, reply icons — should mirror in RTL. Icons that represent real-world objects — a clock, a play button, a checkmark, a phone — must not mirror. A play button pointing left looks broken, not localized.
I ended up with a tiny wrapper that made the decision explicit at the call site:
function DirectionalIcon({ icon: Icon, ...props }) {
return (
<Icon
{...props}
style={[props.style, I18nManager.isRTL && { transform: [{ scaleX: -1 }] }]}
/>
)
}
Auditing every icon in the app for "flips / doesn't flip" sounds tedious. It is. It's also exactly the kind of detail Arabic-speaking users notice immediately.
Arabic text is not just translated text
A few things that bit me:
- Line height. Arabic script has taller ascenders and lower descenders than Latin. The line height that looks fine in English clips diacritics in Arabic. I ended up with per-script typography tokens rather than one global scale.
- Font rendering. The system Arabic fonts are fine, but if the client's brand uses a custom Arabic typeface, test it on low-end Android devices early. Shaping (the way Arabic letters connect) is handled by the platform, but font metrics vary a lot between files.
- Numerals. Iraq mostly uses Eastern Arabic numerals (٠١٢٣) in print but Western numerals (0123) in apps and phone numbers. Don't guess — ask the client, then centralize it in one formatting utility built on
Intl.NumberFormatso you can change your mind cheaply. - Mixed-direction strings. A sentence in Arabic containing an English brand name or a URL is a bidirectional-text minefield. When a string renders in the wrong order, the fix is usually an explicit Unicode direction mark (
, the right-to-left mark) rather than a style change.
Test where the flips happen, not where they don't
By the second app I had a short manual checklist that caught nearly everything:
- Every screen with a horizontal
ScrollViewor carousel — does it start from the correct side? - Every swipeable row — do the actions appear on the expected side, and does the gesture direction match?
- Every text input — is
textAligncorrect, and does the placeholder align with typed text? - Toasts, badges, and absolutely positioned elements — anchored with
start/end? - The one screen everyone forgets: the native parts — alerts, share sheets, notification text.
None of this is hard individually. What makes RTL projects go wrong is treating direction as a rendering detail instead of a product requirement. Put it in the definition of done for every screen, and the last week before launch becomes boring — which is exactly what you want.