Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

arabic-type-rn

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>

1. Select the concrete face. Never pass fontWeight.

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 })} />

2. Arabic needs a taller line box than Latin at the same size.

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.

3. letterSpacing must be 0.

Tracking pulls the joins apart and turns cursive script into disconnected glyphs. The returned style pins it to 0.

Face names are not filenames

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/*.ttf

or 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'),
});

Branch per string, not per locale

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.

API

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).

Test

Requires Node 22.6+ (native TypeScript, no build step, no dependencies).

npm test

License

MIT

About

Arabic text styling for React Native — concrete font faces, a line box that clears tashkil, and zero tracking. Zero dependencies.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages