Arabic text styling for React Native. Zero dependencies.
Three rules, each one learned from a bug that shipped.
import { arabicTextStyle, isArabic } from 'arabic-type-rn';
<Text style={isArabic(label) ? arabicTextStyle({ fontSize: 17, weight: 600 }) : latinStyle}>
{label}
</Text>React Native will happily stack a synthetic bold on top of a real bold face. In Latin that is merely ugly. In Arabic it breaks cursive joining — the letters stop connecting and the word comes apart.
arabicTextStyle() resolves the weight to a PostScript face name and returns a
style with no fontWeight at all. Spread it last so nothing puts one back.
// ✗ synthetic bold over a real bold — joins break
<Text style={{ fontFamily: 'IBMPlexSansArabic-Bold', fontWeight: '700' }} />
// ✓
<Text style={arabicTextStyle({ fontSize: 17, weight: 700 })} />Latin UI type sits at 1.2–1.3. Anything in that range clips Arabic ascenders, descenders, and tashkīl. The damma on «قُيّم» is the first thing to go — and it gets reported as "the Arabic looks blurry", never as "the text is clipped", which sends you hunting through font smoothing for a day.
Default here is 1.45, or ARABIC_LINE_HEIGHT_DISPLAY (1.4) for display
sizes where 1.45 opens too much space. Row minHeights absorb the extra point
or two.
Tracking pulls the joins apart and turns cursive script into disconnected
glyphs. The returned style pins it to 0.
IBM_PLEX_SANS_ARABIC maps each weight to a PostScript name. A file called
IBMPlexSansArabic-SemiBold.ttf is not a promise about the name iOS registers
it under.
Read the real names off the binaries:
fc-scan --format '%{postscriptname}\n' assets/fonts/*.ttfor on macOS, Font Book → select the face → ⌘I.
Getting this wrong fails silently: the system substitutes a default face, the text still renders, and nobody notices until someone asks why the Arabic looks different from the mockup.
With expo-font, register by that same name:
useFonts({
'IBMPlexSansArabic-Regular': require('./assets/fonts/IBMPlexSansArabic-Regular.ttf'),
'IBMPlexSansArabic-SemiBold': require('./assets/fonts/IBMPlexSansArabic-SemiBold.ttf'),
'IBMPlexSansArabic-Bold': require('./assets/fonts/IBMPlexSansArabic-Bold.ttf'),
});isArabic(text) tests for Arabic script. Use it on the string you are about to
render, not on the app locale: an English UI still shows Arabic merchant names,
and an Arabic UI still shows "Starbucks".
Arabic-Indic digits (٠–٩) count as Arabic — they need the Arabic face too.
arabicTextStyle({ fontSize, weight?, lineHeightMultiplier?, faces? }) |
{ fontFamily, fontSize, lineHeight, letterSpacing: 0 } |
isArabic(text) |
contains Arabic script |
IBM_PLEX_SANS_ARABIC |
weight → PostScript name |
ARABIC_WEIGHTS |
[400, 600, 700] |
ARABIC_LINE_HEIGHT |
1.45 |
ARABIC_LINE_HEIGHT_DISPLAY |
1.4 |
Pass faces to use a different family (Cairo, Tajawal, IBM Plex Sans Arabic —
anything with the three weights registered).
Requires Node 22.6+ (native TypeScript, no build step, no dependencies).
npm testMIT