Building Bilingual-First — Arabic and English Web Apps
Practical lessons on building RTL-native web applications, from CSS logical properties to font pairing and cultural adaptation.
Not Retrofitted — Native
Most bilingual web apps start in English and bolt on Arabic later. You can tell. The spacing is off, the fonts clash, the layout breaks in subtle ways that only Arabic readers notice.
MOELABS.DEV and all my products are built bilingual-first. Arabic isn’t an afterthought — it’s a constraint that shapes every design decision from day one.
Here’s what I’ve learned.
CSS Logical Properties Are Non-Negotiable
Stop writing margin-left and padding-right. Use logical properties:
/* ❌ Breaks in RTL */
.card {
margin-left: 1rem;
border-right: 1px solid #222;
}
/* ✅ Works in both directions */
.card {
margin-inline-start: 1rem;
border-inline-end: 1px solid #222;
}
Logical properties map to the reading direction, not the physical screen. inline-start is left in English and right in Arabic. One codebase, both directions.
The same applies to:
padding-inline-start/padding-inline-endborder-inline-start/border-inline-endinset-inline-start/inset-inline-endtext-align: start(notleft)
Font Pairing: Archivo + Cairo
Finding fonts that work across Latin and Arabic scripts is hard. My stack:
- Archivo (Latin) — geometric, high x-height, reads well at small sizes
- Cairo (Arabic) — designed specifically for Arabic digital text, similar weight and proportions to Archivo
The key is matching visual weight and x-height across scripts. Cairo at 600 weight roughly matches Archivo at 700. I handle this with CSS custom properties:
:root {
--font-display: 'Archivo', sans-serif;
--font-body: 'Archivo', sans-serif;
}
html[dir="rtl"] {
--font-display: 'Cairo', 'Archivo', sans-serif;
--font-body: 'Cairo', 'Archivo', sans-serif;
}
Line Height Differences
Arabic text needs more vertical space than Latin text. Arabic characters have more ascenders, descenders, and diacritical marks.
My baseline:
- English body:
line-height: 1.55 - Arabic body:
line-height: 1.75 - English headings:
line-height: 0.92 - Arabic headings:
line-height: 1.15
Letter Spacing
Latin uppercase text benefits from positive letter-spacing (tracking-wider in Tailwind). Arabic text should never have letter-spacing — it breaks the connected letterforms:
html[dir="rtl"] .nav-link {
letter-spacing: 0 !important;
}
Icon Direction
Directional icons (arrows, external links) must flip in RTL:
html[dir="rtl"] .lucide-arrow-right {
transform: scaleX(-1);
}
But not all icons flip. A search icon, a settings gear, or a close X should stay the same in both directions. Only icons that imply direction should mirror.
The i18n Architecture
For a static site, I use a simple client-side approach:
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.getAttribute('data-i18n');
el.textContent = translations[currentLang][key];
});
For elements containing HTML (bold text, links), I use data-i18n-html and innerHTML. The toggle switches dir="rtl" and lang="ar" on the <html> element.
Cultural, Not Just Linguistic
Translation is the minimum. Cultural adaptation is the goal:
- Numbers: Arabic-Indic numerals (٠١٢٣) vs Western Arabic (0123) — I use Western Arabic in both since my audience expects it in tech contexts
- Date formats: context-dependent
- Reading patterns: Arabic readers scan from right to left, so primary actions should be on the right in RTL
The Payoff
Building bilingual-first costs maybe 15% more upfront. But it saves enormous pain later — no “RTL migration sprint,” no layout bugs, no angry users.
If your market includes Arabic speakers, build for them from day one. They’ll notice the difference.