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)

The three families, with their code points
familyrangedigitsread in
EuropeanU+0030–U+00390123456789Algeria, Morocco, Tunisia, Libya
Arabic-IndicU+0660–U+0669٠١٢٣٤٥٦٧٨٩Egypt, Saudi Arabia, Iraq, the Gulf
Eastern Arabic-IndicU+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

  1. Pick the system per market, not per language. Arabic is spoken across all three.
  2. Make the formatter the only place that decides, so a template cannot hard-code digits.
  3. Accept typed digits in all three systems at every input that takes a number.
  4. 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

Compare the systems