Arabic and Persian typography for the web
Arabic letters join, reach further above and below the line than Latin, and can read smaller at the same size. Type settings made for English break them in ways a native reader sees at once.

The short answer
Arabic web fonts are typefaces drawn for the Arabic script, which also writes Persian, and served to the browser with the page. Choose one that covers the language you publish, because Persian needs letters and digits that Arabic does not use. Name it directly in the font stack after your Latin family. Then set it by its own rules: no letter-spacing, a taller line height, and a size judged by eye against the Latin beside it. Vazirmatn, IBM Plex Sans Arabic, Noto Naskh Arabic and Amiri are open families on Google Fonts.
- Arabic letters join, so letter-spacing breaks words. Stretch Arabic deliberately, with kashida, never with tracking.
- Arabic reaches further above and below the line than Latin and can read smaller at the same size: open the line height, and scale the Arabic face rather than the whole line.
- Persian needs four extra letters, its own yeh and kaf, the zero-width non-joiner and its own digits. Test a font with real Persian before you commit to it.
- Name the Arabic family directly in the stack. A generated Latin fallback with no unicode-range can catch every Arabic letter first.
On this page · 13 sections
- What changes when the script is Arabic
- Joined letters: why tracking breaks Arabic
- Stretching Arabic on purpose
- Size and line height across two scripts
- Persian is more than Arabic with extra letters
- Choosing an Arabic-script family
- A weight number is only a request
- Pairing a Latin family with an Arabic one
- The fallback trap
- A CSS checklist for Arabic-script type
- What this means for an IB's page
- Questions
- Sources
What changes when the script is Arabic
Most web type is tuned for Latin: tight tracking, close display lines, weights chosen by number. Applied to Arabic or Persian, those settings pull letters apart and make wrapped lines collide. Setting a page right-to-left fixes none of this; direction is covered in publishing right-to-left. This article covers the type. Three facts drive it: the letters join, the ascenders and descenders extend much further than Latin ones, and Persian uses characters Arabic does not2.
Joined letters: why tracking breaks Arabic
Arabic is cursive: the only spaces inside a word sit next to letters that do not join on both sides2. The CSS specification says cursive scripts "do not admit gaps between their letters"3. Faced with letter-spacing, a browser may stretch the joins or leave the letters unspaced, and authors are advised to avoid it unless they accept results that differ between browsers3.
Tracking also costs ligatures. When spacing between characters is not zero, browsers should not apply optional ligatures34, and in OpenType only the rlig feature counts as required3. Gravity sets letter-spacing to zero on Arabic-script labels and headings, because tracking breaks joined letters1.
Stretching Arabic on purpose
Arabic widens words its own way. Kashida lengthens the connection between joined letters; tatweel, U+0640, is a fixed-width character inserted between them2. Both justify lines and can make a heading more visible, but neither is letter-spacing: the stretch runs along the baseline, unevenly, and too much of it makes the text look patchy2. Tatweel is part of the text, so it is copied and indexed with the word; keep it for display lines you control. CSS forbids justification from opening gaps between joined Arabic letters but does not require elongation3, so test justified Arabic in each browser you support.
Size and line height across two scripts
Arabic's tall ascenders and deep descenders need room, or a wrapped headline collides with itself. Publishing right-to-left gives Gravity's values; check yours on real headlines that wrap.
Size is harder: at the same font-size, the two scripts often do not look the same size. The W3C lists "the size issue" in mixed texts among the points to weigh when choosing an Arabic font2, and its own example of styling by language sets Arabic at 120%5. But a font-size on :lang(ar) also enlarges any Latin inside, such as a brand name in an Arabic headline. The size-adjust descriptor scales one font face instead, multiplying its glyph outlines and metrics; it has worked across browsers since September 20237. Declare the Arabic face with size-adjust, and only its glyphs grow.
Persian is more than Arabic with extra letters
Persian and Arabic share a script but not a character set. Persian adds پ, چ, ژ and گ. It writes ی (U+06CC) and ک (U+06A9) where Arabic writes ي (U+064A) and ك (U+0643)2. A font must include the Persian forms, and draw them well, before it can set Persian.
Persian also relies on the zero-width non-joiner, U+200C, an invisible character that stops two letters within a word from joining. The W3C marks it as used in Persian, not Arabic, and says such control characters should be kept when text is stored and displayed2. Delete it and letters join that should not; turn it into a space and one word becomes two.
Digits are characters too; which set each audience reads is covered in the pillar article. Put the digits you want in the text rather than relying on a font. Intl.NumberFormat's numberingSystem option has a locale-dependent default10, so name arabext, the Extended Arabic-Indic digits of a Persian edition, or latn explicitly11. Gravity keeps Latin digits in its market ticker1.
Choosing an Arabic-script family
All four families below are on Google Fonts under the SIL Open Font License.
| Family | Style | Weights | Variable | Designed by |
|---|---|---|---|---|
| Vazirmatn | Sans-serif | 100 to 900 | Yes, one weight axis | Saber Rastikerdar13 |
| IBM Plex Sans Arabic | Sans-serif | 100, 200, 300, 400, 500, 600, 700 | No | Mike Abbink, Bold Monday, Khajag Apelian, Wael Morcos14 |
| Noto Naskh Arabic | Serif (Naskh) | 400 to 700 | Yes, one weight axis | Google15 |
| Amiri | Serif (Naskh) | 400 and 700, each with an italic | No | Khaled Hosny, Sebastian Kosch16 |
Vazirmatn describes itself as a Persian/Arabic font for web pages and applications18; Amiri is a classical Naskh for books and running text17; Google Fonts lists Persian among Noto Naskh Arabic's languages15. Whatever a listing says, test with real Persian: the four extra letters, ی and ک in every position, a zero-width non-joiner, and the digits ۴۵۶.
A weight number is only a request
A static family renders only the weights it has. For a missing weight, the browser substitutes by rule: below 400 it tries lighter weights first, then heavier; above 500, heavier first9. Amiri has only 400 and 70016, so a headline set at 300 renders in Amiri Regular and one at 600 in Amiri Bold. A variable family renders any weight on its axis; Gravity's own pages set Vazirmatn at 100 for display, 200 for titles and 300 for ledes1. Two families can also divide the work: the Arabic edition of Gravity's Luxury template uses Amiri for display and IBM Plex Sans Arabic for the interface1.
Pairing a Latin family with an Arabic one
Arabic-script pages still carry Latin: brand names, handles, tickers. The font stack's order decides which family draws it. Many Arabic families include Latin glyphs; Vazirmatn's come from Roboto, and the project also publishes a version without them18. List the Latin family first and the Arabic one second, so Latin characters use the Latin family and Arabic letters fall through to the Arabic one. Then match the two by eye, not by weight number, adjusting the Arabic face's weight and size-adjust until they look consistent.
Scope Arabic styles with :lang() rather than classes: it matches elements that inherit their language from an ancestor, and :lang(fa) also matches fa-IR5. It depends on the language being declared, usually with the lang attribute6. Latin handles inside Arabic also need isolating, with dir="auto" or bdi, or "@handle" displays as "handle@"1; that bidirectional text problem is covered in the pillar article.
The fallback trap
Web fonts arrive after the page, so frameworks generate stand-in faces to hold the layout; the Next.js font loader adds an automatic fallback by default to reduce layout shift12. A stand-in is an ordinary @font-face rule, and a rule with no unicode-range covers every character: the initial value is U+0-10FFFF8.
There are two fixes. Gravity names the families directly, in order: Latin family, Arabic family, then the generated fallback, so Arabic letters reach the Arabic face first1. If you write the fallback face yourself, give it a Latin-only unicode-range; a face is used, and downloaded, only for characters in its range8.
A CSS checklist for Arabic-script type
/* Latin family first, the Arabic face by name, fallbacks last. */
:root {
--font-text: "Latin Sans", "Arabic Sans", "Latin Sans Fallback",
system-ui, sans-serif;
}
/* The Arabic face: Arabic code points only, scaled to sit
level with the Latin beside it. */
@font-face {
font-family: "Arabic Sans";
src: url("/fonts/vazirmatn.woff2") format("woff2");
font-weight: 100 900;
unicode-range: U+0600-06FF, U+0750-077F, U+200C-200D,
U+FB50-FDFF, U+FE70-FEFF;
size-adjust: 108%; /* illustrative: judge by eye */
}
/* A fallback you write yourself: Latin only, so it can
never claim an Arabic letter. */
@font-face {
font-family: "Latin Sans Fallback";
src: local("Arial");
unicode-range: U+0000-00FF;
}
/* Arabic-script settings, scoped by language. */
:lang(ar),
:lang(fa) {
letter-spacing: 0;
line-height: 1.8; /* illustrative */
}- lang and dir on the html element, so :lang() has a language to match.
- letter-spacing: 0 for Arabic and Persian, labels and buttons included.
- Stretching only on purpose, with kashida or tatweel.
- A taller line height, checked on headlines that wrap.
- Arabic size matched by eye, with size-adjust where scripts share a line.
- The Arabic family named after the Latin one, before any generated fallback.
- Your own fallback faces limited by unicode-range.
- Persian tested with پ چ ژ گ, ی, ک, a zero-width non-joiner and ۴۵۶.
- Digits produced as characters, numbering system named.
What this means for an IB's page
For an introducing broker whose audience reads Arabic or Persian, the type is seen before a word is read, and broken words or colliding lines say the page was made for someone else. The risk warning needs the same care: it only protects readers who can read it comfortably. A page that reads natively is the point of an IB website over a social feed.
Questions
What is the best Arabic web font?
Should I use letter-spacing on Arabic text?
Why does my Arabic text render in Arial?
Sources
- What Gravity learned setting Arabic-script type — Gravity
- CSS Text Module Level 3 — W3C
- letter-spacing — MDN Web Docs
- Styling using language attributes — W3C Internationalization
- :lang() — MDN Web Docs
- size-adjust — MDN Web Docs
- unicode-range — MDN Web Docs
- font-weight — MDN Web Docs
- Intl.NumberFormat() constructor — MDN Web Docs
- Intl.supportedValuesOf(): supported numbering system types — MDN Web Docs
- Font Module — Next.js
- Vazirmatn: family metadata — Google Fonts (google/fonts repository)
- IBM Plex Sans Arabic: family metadata — Google Fonts (google/fonts repository)
- Noto Naskh Arabic: family metadata — Google Fonts (google/fonts repository)
- Amiri: family metadata — Google Fonts (google/fonts repository)
- Amiri: description — Google Fonts (google/fonts repository)
- Vazirmatn — Vazirmatn project, GitHub
Terms in this article
Gravity Editorial
Written by the people who build Gravity, from what the product does and what building it taught us. Figures come from Gravity's own measurements or from the source cited beside them; nothing on these pages is trading advice. See how we write.
Related in the Journal
Direction is one attribute. Reading natively is everything after it — the handles, tickers, numbers and type that run through an Arabic or Persian page.
A feed is where people find you. A website is where they decide whether to trust you — the one place your offer, your broker and your risk warning sit together.
An IB's marketing is one system, not a feed: attention, trust, a disclosed link and a reason to stay. What each channel does, what advertising may do, and where the rules draw the line.
One profile is all Gravity needs to begin.
Give Gravity a public Instagram, TikTok or YouTube profile. It reads it and builds your website in your own language — in minutes, with nothing on it you can't stand behind.
