4 min read · 2026-10-26
Three families of numerals
Western, Arabic-Indic and Eastern Arabic-Indic digits, which countries write which, and why the decision belongs to your formatter.
The W3C's Arabic Layout Requirements open the numbers section by naming three families of digits used with the Arabic script, and then name the regions each one belongs to: European numerals in the western Arabic-speaking countries, Arabic-Indic in the eastern ones, Eastern Arabic-Indic in Iran and Afghanistan. (§6.1.2)
| family | range | digits | read in |
|---|---|---|---|
| European | U+0030–U+0039 | 0123456789 | Algeria, Morocco, Tunisia, Libya |
| Arabic-Indic | U+0660–U+0669 | ٠١٢٣٤٥٦٧٨٩ | Egypt, Saudi Arabia, Iraq, the Gulf |
| Eastern Arabic-Indic | U+06F0–U+06F9 | ۰۱۲۳۴۵۶۷۸۹ | Iran, Afghanistan |
Read the first and third rows again: ۴ is not ٤. They look
almost identical and are different code points, so a Persian number set in a font that
only covers U+0660–U+0669 falls back mid-number and changes typeface halfway through.
There is no CSS property for this
Nothing in CSS says "use Arabic-Indic digits". The digits come from the string, or from the locale a formatter was given. That means the decision lives in your data flow, and it is usually made by whoever typed the number into the template.
new Intl.NumberFormat('en-GB').format(1234.5) // "1,234.5"
new Intl.NumberFormat('ar-EG').format(1234.5) // "١٬٢٣٤٫٥"
new Intl.NumberFormat('ar-MA').format(1234.5) // "1 234,5"
new Intl.NumberFormat('fa-IR').format(1234.5) // "۱٬۲۳۴٫۵"
Note the third line. Morocco is an Arabic-speaking country whose CLDR data formats Western digits, with a space as the group separator and a comma for the decimal. If your product ships one "Arabic" locale and expects Arabic-Indic numerals, Morocco is already wrong — and so is Algeria.
The input that quietly refuses to cooperate
<input type="number"> accepts a valid floating-point number, and a
valid floating-point number is written with ASCII digits. Paste
١٢٣٤ into one and the value is empty; the browser is not being rude, it is
following the spec. For an Arabic interface that accepts Arabic-Indic digits, use
type="text" with inputmode="numeric" and convert on input.
const arabic = /[\u0660-\u0669]/;
const toAscii = (s) => s.replace(/[\u0660-\u0669]/g,
(d) => String(d.codePointAt(0) - 0x0660));
// "١٢٣٤" -> "1234"
Tabular figures differ per family
font-variant-numeric: tabular-nums asks the font for fixed-width figures,
and whether it can honour that is a property of the font, not of the CSS. In a table of
prices, a family without tabular Arabic figures will still wobble per row. Set the
property, then look at three rows containing ١١١ and ٨٨٨ before deciding.
What to decide, and where
- Pick the system per market, not per language. Arabic is spoken across all three.
- Make the formatter the only place that decides, so a template cannot hard-code digits.
- Accept typed digits in all three systems at every input that takes a number.
- Check the font covers the code points you chose, or the fallback will pick for you.
The numerals tool shows one number in all three systems
beside what your own browser's Intl produces, which is the only way to see
whether your formatter and your copy agree.
Sources
- W3C Arabic Layout Requirements §6.1.2 — the three families and their regions.
- MDN: Intl.NumberFormat
- HTML: valid floating-point number