Adding dir="rtl" to your HTML tag gets you about 60% of the way to an Arabic-ready app and gives you a false sense of being done. The other 40% is layout that mirrors correctly, icons that flip, mixed Arabic-English text that doesn't shatter the line, fonts that load in Arabic without choking the page, and forms that handle numbers and punctuation sanely. The fix is to lean on CSS logical properties (ms-, pe-, start-*), use Tailwind's rtl: variant for direction-aware classes, gate Arabic fonts behind the active language, wrap embedded LTR runs in <bdi>, and test with real Arabic content, never with mirrored lorem ipsum. Build this in from day one. Retrofitting it onto a finished LTR app takes longer than rebuilding the layout layer from scratch.
direction: rtlMost tutorials tell you to add dir="rtl" to your HTML tag and call it a day. That gets you maybe 60% of the way. The other 40% is where things get tricky, and where most apps targeting Arabic speakers fall apart.
I've shipped close to a dozen bilingual Arabic-English apps in the last three years, for Qobouli students, for Maka Media, for various clients across Istanbul and Riyadh. The first one took weeks of unscheduled cleanup because I treated RTL as a configuration flag instead of a design dimension. The last few took zero unscheduled cleanup, because I now build for both directions from the first line of code.
This is what I wish someone had handed me three years ago.
Every margin-left becomes margin-right. Every padding-left becomes padding-right. Every flex-row visually reverses. Every absolute-positioned element pinned with left: 0 ends up on the wrong side of the screen. If you've hardcoded directional values anywhere in your codebase, you'll need to change them all, and the worst ones are the ones you forgot, which only show up in a screenshot a client sends you a week after launch.
The fix: Use logical properties. In Tailwind, that means ms-4 (margin-inline-start) instead of ml-4, pe-6 (padding-inline-end) instead of pr-6, start-0 instead of left-0, end-2 instead of right-2. These automatically flip based on direction. In vanilla CSS, the modern equivalents are margin-inline-start, padding-inline-end, inset-inline-start, and so on.
The rule I now follow: the words "left" and "right" are bugs. Every time I see them in a class name or style rule, I treat them as a code smell that needs justification.
Back arrows, chevrons, progress indicators, next/previous buttons, send-message icons: anything that implies a direction needs to flip in RTL. A back arrow pointing left in English should point right in Arabic, because in Arabic "back" is to the right.
The fix: Add rtl:rotate-180 to directional icons in Tailwind, or write a CSS rule like [dir="rtl"] .directional-icon { transform: rotateY(180deg); }. For icon libraries, build a small <DirectionalIcon> wrapper that handles this once.
Be careful not to over-flip. A play button, a heart, a checkmark, a user avatar: these have no direction and should never flip. The trick is to keep a tight list of which icons are directional, and never auto-flip everything.
Arabic fonts are significantly larger than Latin fonts, often 3 to 5 times the file size, because Arabic needs many more glyph variations for ligatures and contextual forms. Loading both fonts upfront hurts performance on every page, even for users who only see one of them.
The fix: Load the Arabic font only when the active language is Arabic, and the Latin font only when English. Use font-display: swap to prevent layout shift, and preload the active font in the <head>. If you serve from a CDN, request only the glyph subset you actually use, for most apps you can drop file size by 70% by subsetting.
For my projects I typically use Cairo or IBM Plex Sans Arabic for Arabic, and Inter or Geist for English. I switch them dynamically based on the active language, and I never ship both at the same time.
Arabic text with embedded English technical terms ("تطبيق React مع TypeScript") produces bidirectional text. The browser's bidi algorithm handles most cases well, but numbers, punctuation, parentheses, and quotation marks can flip in surprising ways, especially when they're adjacent to brackets or hyphens.
The fix: Wrap embedded LTR runs in <bdi> tags so the bidi algorithm treats them as isolated units. For numbers in Arabic UIs, decide explicitly whether you want Arabic-Indic digits (٠١٢٣) or Western digits (0123) and apply a consistent rule. Most Arabic-speaking users today prefer Western digits for technical content, but Arabic-Indic still feels right in editorial copy.
Form inputs deserve special attention. An input with type="email" should always be LTR, even on an Arabic page, because email addresses are LTR by nature. Same for URLs, phone numbers, and password fields. A bilingual form that gets this wrong looks broken even to users who can't articulate why.
The fix: Set dir="ltr" directly on inputs that hold LTR content, regardless of the page direction. Most form libraries support this out of the box once you tell them.
A slide-in animation that enters from the left in English should enter from the right in Arabic, otherwise it feels backwards. Same for swipe gestures, drawer pull-outs, and carousel navigation.
The fix: Use logical transform values where possible (transform: translateX() with a value derived from the direction), or define two animation keyframes, one for LTR, one for RTL, and select via the [dir] attribute.
I use a LanguageProvider context that:
t() function for translationsdir and lang on the document root via a side effectA simplified version looks like this:
function LanguageProvider({ children }: { children: React.ReactNode }) {
const [language, setLanguage] = useState<'en' | 'ar'>(() => {
return (localStorage.getItem('lang') as 'en' | 'ar') ?? 'en';
});
useEffect(() => {
document.documentElement.lang = language;
document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr';
localStorage.setItem('lang', language);
}, [language]);
return (
<LanguageContext.Provider value={{ language, setLanguage, t: makeT(language) }}>
{children}
</LanguageContext.Provider>
);
}
Translations live in JSON files (en.json, ar.json) loaded on demand or imported statically depending on whether the app is content-heavy. For small apps I import them statically; for larger ones I lazy-load by route.
Always test with real Arabic content. Mirrored lorem ipsum will not expose bidi issues, font-rendering problems, ligature breakage, or layout failures caused by genuinely longer Arabic words ("registration" is 12 characters; "التسجيل" is 7 but renders much wider in some fonts).
A reasonable test matrix:
I keep a small "RTL smoke test" page in every bilingual project: ten components rendered in both languages side by side. Before shipping, I look at it. It catches things I'd miss in production for months.
left: 0 instead of inset-inline-start: 0Every one of those bugs was a one-line fix. The work was finding them.
If you're building for Arabic speakers, RTL support isn't a nice-to-have. It's a requirement. And it's an order of magnitude easier to build in from day one than to retrofit later. Pick logical properties from the very first commit, write a smoke-test page, ship with real Arabic content, and you'll save weeks of post-launch cleanup.
The Arab market is enormous, underserved, and quietly hungry for products that respect their language. Get this right and you have a real edge.
إضافة dir="rtl" لعنصر HTML توصلك إلى 60% من تطبيق جاهز للعربية وتعطيك إحساساً مزيّفاً بأنك انتهيت. الـ 40% الباقية هي تخطيط ينعكس صحيحاً، أيقونات تنقلب، نص عربي مختلط بإنجليزي لا يتشظّى عند السطر، خطوط عربية تُحمَّل دون اختناق الصفحة، وحقول نماذج تتعامل مع الأرقام وعلامات الترقيم بمنطق. الحلّ يقوم على خصائص CSS المنطقيّة (ms-، pe-، start-*)، واستخدام متغيّر rtl: في Tailwind لما يعتمد على الاتجاه، وتقييد الخطوط العربية باللغة النشطة، وتغليف المقاطع الإنجليزية المضمّنة بـ <bdi>، والاختبار بمحتوى عربي حقيقي لا بـ Lorem Ipsum معكوس. ابنِ هذا من اليوم الأول. الترميم على تطبيق LTR منجَز يأخذ وقتاً أطول من إعادة بناء طبقة التخطيط من الصفر.
direction: rtlمعظم الدروس تقول لك أن تضيف dir="rtl" لعنصر HTML وتنتهي. هذا يوصلك إلى 60% من الطريق. الـ 40% الباقية هي حيث تصبح الأمور صعبة، وحيث تفشل معظم التطبيقات التي تستهدف المتحدّثين بالعربية.
شحنت قرابة عشرة تطبيقات عربية-إنجليزية في السنوات الثلاث الأخيرة: لطلّاب قبولي، لـ Maka Media، ولعملاء بين إسطنبول والرياض. الأوّل كلّفني أسابيع ترميم غير مجدولة لأنني تعاملت مع RTL كعَلَم إعداد لا كبُعد تصميم. الأخيرة لم تكلّف أيّ ترميم، لأنني صرت أبني للاتّجاهين من السطر الأوّل.
هذا ما تمنّيت لو سلّمه لي أحد قبل ثلاث سنوات.
كل margin-left يصبح margin-right. كل padding-left يصبح padding-right. كل flex-row ينعكس بصرياً. كل عنصر مطلق الموضع مثبَّت بـ left: 0 ينتهي على الجانب الخطأ من الشاشة. لو ربطت قيماً اتجاهيّة في أي مكان في الكود، ستضطر لتغييرها كلها، وأسوأها ما نسيته، الذي يظهر في لقطة شاشة يرسلها لك العميل بعد الإطلاق بأسبوع.
الحلّ: استخدم الخصائص المنطقيّة. في Tailwind يعني هذا ms-4 (margin-inline-start) بدل ml-4، وpe-6 بدل pr-6، وstart-0 بدل left-0، وend-2 بدل right-2. هذه تنقلب تلقائياً بحسب الاتجاه. في CSS العادي المكافئات الحديثة هي margin-inline-start وpadding-inline-end وinset-inline-start وما شابه.
القاعدة التي صرت أتّبعها: كلمتا "left" و"right" أخطاء. كل مرة أراهما في اسم كلاس أو قاعدة style أعاملهما كرائحة كود تحتاج تبريراً.
أسهم الرجوع، الشيفرونات، مؤشّرات التقدّم، أزرار التالي والسابق، أيقونات الإرسال: أيّ شيء يلمّح إلى اتجاه يحتاج الانعكاس في RTL. سهم الرجوع المتّجه يساراً في الإنجليزية يجب أن يتّجه يميناً في العربية، لأن "الرجوع" في العربية يميناً.
الحلّ: أضف rtl:rotate-180 للأيقونات الاتجاهيّة في Tailwind، أو اكتب قاعدة CSS مثل [dir="rtl"] .directional-icon { transform: rotateY(180deg); }. لمكتبات الأيقونات، ابنِ غلافاً صغيراً <DirectionalIcon> يتولّى الأمر مرة واحدة.
احذر المبالغة في القلب. زرّ التشغيل، القلب، علامة الصحّ، صورة الحساب: هذه بلا اتجاه ولا يجب أن تنقلب أبداً. الحيلة الحفاظ على قائمة ضيّقة لما هو اتجاهيّ وعدم قلب كل شيء آلياً.
الخطوط العربية أكبر بكثير من الخطوط اللاتينيّة: غالباً 3 إلى 5 أضعاف حجم الملفّ، لأن العربية تحتاج تنويعات Glyph كثيرة للروابط والأشكال السياقيّة. تحميل الخطّين مقدَّماً يؤذي الأداء على كل صفحة، حتى لمستخدمين يرون لغة واحدة.
الحلّ: حمّل الخطّ العربي فقط حين تكون اللغة النشطة عربيّة، واللاتيني حين تكون إنجليزيّة. استعمل font-display: swap لتفادي قفز التخطيط، وعمل Preload للخطّ النشط في <head>. لو تخدم من CDN، اطلب فقط الـ Subset من الـ Glyphs التي تستخدمها فعلاً, لمعظم التطبيقات يمكنك تقليل الحجم 70% بالـ Subsetting.
في مشاريعي عادة أستخدم Cairo أو IBM Plex Sans Arabic للعربية، وInter أو Geist للإنجليزيّة. أبدّل بينهما ديناميكياً حسب اللغة النشطة، ولا أشحن الاثنين دفعة واحدة أبداً.
النصّ العربي مع مصطلحات تقنيّة إنجليزيّة مضمّنة ("تطبيق React مع TypeScript") يُنتج نصاً ثنائي الاتجاه. خوارزميّة Bidi في المتصفّح تتعامل مع معظم الحالات بشكل جيّد، لكن الأرقام وعلامات الترقيم والأقواس وعلامات الاقتباس قد تنقلب بطرق مفاجئة، خصوصاً حين تجاور الأقواس أو الشرطات.
الحلّ: غلّف المقاطع الإنجليزيّة المضمّنة بـ <bdi> ليتعامل معها Bidi كوحدات معزولة. للأرقام في واجهات عربيّة، قرّر صراحةً هل تريد الأرقام الهنديّة (٠١٢٣) أم الغربية (0123) وطبّق قاعدة موحّدة. معظم المستخدمين العرب اليوم يفضّلون الأرقام الغربيّة للمحتوى التقنيّ، لكن الهنديّة ما زالت تبدو أصحّ في المحتوى التحريريّ.
حقول النماذج تستحقّ اهتماماً خاصاً. حقل type="email" يجب أن يبقى LTR دائماً حتى في صفحة عربيّة، لأن عناوين البريد LTR بطبيعتها. نفس الشيء للروابط وأرقام الهواتف وحقول كلمات السرّ. نموذج ثنائي اللغة يخطئ هذا يبدو معطوباً حتى لمستخدمين لا يستطيعون التعبير عن السبب.
الحلّ: ضع dir="ltr" مباشرة على الحقول التي تحمل محتوى LTR، بصرف النظر عن اتّجاه الصفحة. معظم مكتبات النماذج تدعم هذا حالما تُعلمها.
حركة Slide-in تدخل من اليسار في الإنجليزية يجب أن تدخل من اليمين في العربية، وإلا أحسّ المستخدم أنها مقلوبة. نفس الشيء لحركات السحب والأدراج وتنقّل الـ Carousel.
الحلّ: استخدم قيم Transform منطقيّة حيث أمكن (transform: translateX() بقيمة مشتقّة من الاتجاه)، أو عرّف نسختين من الـ Keyframes, واحدة لـ LTR وأخرى لـ RTL, واختر عبر سمة [dir].
أستخدم LanguageProvider كسياق React يقوم بـ:
t() للترجماتdir وlang على جذر الوثيقة عبر تأثير جانبيّنسخة مبسَّطة تبدو هكذا:
function LanguageProvider({ children }: { children: React.ReactNode }) {
const [language, setLanguage] = useState<'en' | 'ar'>(() => {
return (localStorage.getItem('lang') as 'en' | 'ar') ?? 'en';
});
useEffect(() => {
document.documentElement.lang = language;
document.documentElement.dir = language === 'ar' ? 'rtl' : 'ltr';
localStorage.setItem('lang', language);
}, [language]);
return (
<LanguageContext.Provider value={{ language, setLanguage, t: makeT(language) }}>
{children}
</LanguageContext.Provider>
);
}
الترجمات تعيش في ملفّات JSON (en.json، ar.json) تُحمَّل عند الطلب أو تُستورَد ثابتة حسب كثافة المحتوى. للتطبيقات الصغيرة أستوردها ثابتة، وللكبيرة أحمّلها كسولاً حسب المسار.
اختبر دائماً بمحتوى عربيّ حقيقيّ. Lorem Ipsum معكوس لن يكشف مشاكل Bidi ولا مشاكل رسم الخطّ ولا انكسار الـ Ligatures ولا فشل التخطيط بسبب كلمات عربيّة أطول فعلاً ("registration" 12 حرفاً؛ "التسجيل" 7 أحرف لكنها تُرسم أعرض في بعض الخطوط).
مصفوفة اختبار معقولة:
أحتفظ بصفحة "اختبار دخان RTL" صغيرة في كل مشروع ثنائي اللغة: عشرة مكوّنات مرسومة باللغتين جنباً إلى جنب. قبل الإطلاق أنظر إليها. تلتقط أشياء كنت سأفوّتها في الإنتاج لأشهر.
left: 0 بدل inset-inline-start: 0كلّ خطأ منها كان إصلاح سطر واحد. العمل كان في العثور عليها.
لو تبني للمتحدّثين بالعربيّة، دعم RTL ليس رفاهيّة. إنه متطلَّب. وهو أسهل ببناءِه من اليوم الأوّل بمراتب من ترميمه لاحقاً. اعتمد الخصائص المنطقيّة من أوّل Commit، اكتب صفحة اختبار دخان، أطلق بمحتوى عربيّ حقيقيّ، وستوفّر أسابيع من الترميم بعد الإطلاق.
السوق العربيّ ضخم ومُهمَل وجائع بهدوء لمنتجات تحترم لغته. أتقن هذا تجد ميزة حقيقيّة.
ما الخطّ العربيّ الذي توصي به؟ للواجهات: Cairo أو IBM Plex Sans Arabic. للمحتوى التحريريّ الطويل: Tajawal أو Almarai. تجنّب Naskh التقليدية في واجهات المنتج, جميلة لكنّها متعِبة على الشاشة.
كيف أتعامل مع لوحة المفاتيح؟ المستخدمون العرب يكتبون بإحدى اللغتين حسب السياق. لا تجبر تخطيط لوحة المفاتيح ولا تعيد توجيه الإدخال. اترك المتصفّح يتولّى.
هل أحتاج إعادة تصميم كاملة للعربيّة؟ لا. التخطيط ينعكس آلياً مع الخصائص المنطقيّة. ما يحتاج تصميماً عربيّاً مخصّصاً هو الطباعة (أحجام، مسافات بين الأسطر) لا التخطيط.
كيف أتعامل مع تواريخ التقويم؟ Intl.DateTimeFormat يعطيك تنسيقاً صحيحاً لـ ar-SA أو ar-EG. للأرقام، حدّد بصراحة Arabic-Indic أو Western واحفظ الاتّساق.
هل اتّجاه RTL يؤثّر على Charts و Maps؟ نعم. مكتبات الرسوم البيانيّة عادة LTR بطبيعتها. اعكس المحاور والتسميات يدوياً، أو اختر مكتبة تدعم RTL أصلياً.
أين أختبر RTL في CI؟ صفحة "اختبار دخان RTL" مع Playwright + Visual Regression تلتقط معظم التراجعات. شغّلها على كل PR.
دعم العربيّة في المنتجات الرقميّة لا يزال ضعيفاً بشكل صادم في 2026. منتجات عالميّة تعالج اليابانيّة والكوريّة بإتقان وتفشل في الأساسيّات العربيّة. هذا فرصة. لو تبني للسوق العربيّ وتنفّذ RTL جيّداً، أنت متقدّم على معظم منافسيك قبل أن تكتب ميزة واحدة. الاستثمار الأوّليّ يدفع نفسه عشرات المرّات في تقدير المستخدمين وفي معدّلات التحويل.
/ar/page و/en/page يساعد SEO ويتيح للمستخدمين مشاركة الروابط باللغة الصحيحة.hreflang في الـ HTML. Google يستخدمها لتقديم النسخة الصحيحة لكلّ مستخدم. غيابها يهبط ترتيب نتائج البحث للنسخة العربيّة بشكل دراماتيكيّ.