Every brief that says “and we’ll need it in Arabic too” hides the same assumption: that a second language is a content task. Swap the strings, flip the direction, ship it.
It is not. Here are the three things that break, in the order they break, and what actually fixes them.
1. The type gets smaller when you flip it
Latin type has an x-height — a whole band of lower-case letters sitting between the baseline and the cap line. Arabic script does not. Every letter occupies the full vertical band, and the connecting strokes, dots and diacritics extend above and below it.
The practical consequence is that Arabic and Kurdish set optically smaller than Latin at the same point size, while simultaneously needing more leading than Latin at the same size. Those two corrections pull in opposite directions, which is why a single “font-size multiplier for RTL” never works.
What does work is separating the corrections by role:
- Body copy scales up — around 1.05× — because the reader needs the same apparent size and the face sets smaller.
- Display type scales down — around 0.8× — because a headline with no lower case and wider words occupies far more area. An English hero line that fits three lines becomes a wall in Kurdish, and the third line disappears below the fold.
- Leading increases at every size. Naskh descenders collide at Latin line-heights, and a heading that looks tight in English looks broken in Arabic.
Three multipliers, not one.
2. Letter-spacing destroys the word
This is the one that gets shipped most often, because it is invisible to anyone who does not read the script.
Arabic and Kurdish are cursive. Letters join, and each one has up to four contextual forms depending on what sits either side of it. Apply positive tracking — the tracked-out eyebrow label that looks so good in Latin — and the joins break. The renderer falls back to isolated forms, and a word becomes a row of disconnected shapes.
The same applies to per-character animation. A “letters fly in one by one” reveal is a lovely effect and it severs every join in the line. If the reveal matters, split by word, never by character, and let the design system enforce it rather than trusting whoever writes the next component.
Uppercase has the same problem in a quieter way: there is no case in Arabic
script, so text-transform: uppercase does nothing at all — which means an
eyebrow style that relies on caps for its hierarchy simply has no hierarchy in
half your languages. It needs a different signal there: colour, weight, or a
rule.
3. Turkish is not “Latin, solved”
Turkish shares an alphabet with English and gets treated as a free extra. Two things bite.
The subset. Turkish needs ğ, ş, ı, İ, ç, ö and ü. Those live in
latin-ext, not latin. Preload only the latin subset — which is what every
performance guide tells you to do — and a Turkish reader watches seven common
letters swap in a beat late on first paint.
The dotless i. i uppercases to İ in Turkish and to I everywhere else.
A CSS text-transform: uppercase gets this right if the element carries
lang="tr", and gets it wrong if the language is only declared on <html> and
the component was rendered from a different locale. It is worth checking, once,
on a real Turkish string.
What this looks like in a design system
The fix is not a set of overrides bolted on at the end. It is one binding at the root:
html[lang='ckb'] {
--font-display: 'Vazirmatn', sans-serif;
--script-scale: 1.03; /* body */
--heading-scale: 0.9;
--display-scale: 0.8; /* display */
--display-tracking: 0; /* never track a connected script */
--eyebrow-transform: none; /* casing is meaningless here */
}
Every component then uses var(--font-display) and
calc(var(--step-6) * var(--display-scale)) and never knows which language it
is in. Adding a fifth language is a block of custom properties, not an audit.
The test is simple, and it is the one worth running before anything ships: open the Kurdish version first. If it looks like the English version with the text replaced, it has not been designed — it has been translated.