4 min read Emadideen Ghannam
Building Arabic-first products: it's not about RTL
Most Arabic support stops at flipping the layout. After shipping Arabic-first apps for the regional market the hard parts are numerals, names, search, and copy.
I have shipped Arabic-first apps for years - government recruitment systems, consumer directories for the local market, multi-tenant platforms with Arabic and English surfaces. Every team I joined that wanted to “support Arabic” started with the same conversation: “we’ll just add an RTL stylesheet.”
RTL is the easy part. RTL is one CSS property. The hard parts are not visual. The hard parts are numerals, names, search, typography, dates, and the words themselves.
1. Numerals
There are two numeric systems in Arabic content: Arabic-Indic numerals (٠١٢٣) and what we call “Arabic numerals” in English (0123). Both are correct in different contexts.
A bank statement in the GCC is more often shown in 0123. Religious or formal text more often in ٠١٢٣. Most apps mix them inconsistently because the dev typed 1234 in JSX and the date library returned ٢٠٢٦. The result reads as careless.
Pick one numeric system per locale and lock it. In CSS:
:lang(ar) {
font-feature-settings: "lnum" 1, "tnum" 1;
}
:lang(ar) .price,
:lang(ar) time {
font-variant-numeric: tabular-nums;
}
In JavaScript, Intl.NumberFormat('ar-EG-u-nu-arab') gives Arabic-Indic; Intl.NumberFormat('ar-EG-u-nu-latn') gives 0123. Pick one in your design system. Document the choice.
2. Names
A government recruitment platform I delivered needed two name fields per candidate: Arabic (the official record) and English (for HR systems and exports). Two columns in the table. Two indexes. Two search vectors.
Most “internationalised” apps assume one name field with locale-driven display. That breaks the moment a recruiter from London opens a CV that exists in Arabic but the candidate also wrote their Latin-script transliteration. The English field is not a translation - it’s a different artefact, often filled in by the candidate themselves.
The schema I converged on:
ALTER TABLE candidates
ADD COLUMN full_name_ar TEXT NOT NULL,
ADD COLUMN full_name_en TEXT,
ADD COLUMN search_vector_ar TSVECTOR,
ADD COLUMN search_vector_en TSVECTOR;
Two name fields. Two tsvector columns. The display logic picks which to show based on the viewer’s locale. Search runs on whichever the user typed in.
3. Search
This is where most “Arabic supporting” apps actually break. Postgres’ default full-text search and pg_unaccent do not handle Arabic well.
The hard cases:
- Hamza variants: ا / أ / إ / آ. A user searching for “احمد” expects to find “أحمد” too.
- Ya variants: ي / ى. Same word, two glyphs.
- Ta marbuta vs Ha: ة / ه. Often interchanged.
- Kashida (tatweel) ـ: sometimes inserted for typographic reasons. Strip before indexing.
- Diacritics (tashkil): ـَ ـُ ـِ. Most users don’t type them. Index without them.
For a directory platform I built I gave up on pg_unaccent and used Elasticsearch’s Arabic analyzer (arabic_normalization + arabic_stemmer + arabic_stop) for the search index, with Postgres as the source of truth. For lighter apps I use a custom Postgres dictionary that normalises hamza, ya, and tatweel before tokenising.
There is no shortcut here. If your search returns nothing for “احمد” but everything for “أحمد”, Arab users will tell their friends your product is broken in two days.
4. Typography
Most “Arabic-supporting” fonts are Western fonts with an Arabic fallback. The fallback is usually an emergency font - DejaVu Sans, Tahoma - that nobody chose. The result reads as a system fallback, not a designed product.
Real Arabic typography needs proper families: Naskh for body, Kufi for display, sometimes Ruq’a for quotes. I pair Inter (English) with one of:
- IBM Plex Sans Arabic - cleanest, free, full weights, good Latin pairing.
- Tajawal - more humanist, Google Fonts, fast to load.
- Cairo - rounder, more friendly, common on Arabic websites.
The CSS:
:root {
--font-en: 'Inter', sans-serif;
--font-ar: 'IBM Plex Sans Arabic', 'Tajawal', sans-serif;
}
html { font-family: var(--font-en); }
:lang(ar) { font-family: var(--font-ar); }
Test on real text, not Lorem Ipsum. Long Arabic words wrap differently. Numbers inside Arabic text behave differently. Hand it to a native speaker before shipping.
5. Date and time
Hijri (Islamic) vs Gregorian. Some government systems require Hijri for legal records and Gregorian for HR exports. So you store both.
Working week boundaries differ by country. Most of the GCC: Sunday to Thursday. Egypt, Tunisia, parts of Levant: also Sunday to Thursday since 2013, but older systems still default to Saturday-start. Lebanon and some private companies: Monday to Friday.
If you build a calendar widget, do not hard-code “Sunday is the start of the week.” Read it from the locale.
6. Copy
This is the easiest to skip and the most embarrassing when you skip it. A literal English-to-Arabic translation of a sign-up form reads like a textbook. Native speakers can tell within two screens that the product was thought-of in English first and translated last.
I work with native copy editors before shipping any Arabic surface. Even now. After 14 years of writing Arabic emails, I still let an editor look at product copy because writing copy is a different skill from speaking the language.
The cost is one consultant for half a day per release. The benefit is the first impression doesn’t read like Google Translate.
Take
RTL is the cosmetic. The product work is in numerals, names, search, and the words themselves. Get those right and the layout almost handles itself.