> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# CSS للعربية واتجاه RTL: الخصائص المنطقية Logical Properties
- URL: https://arabroot.io/articles/css-للواجهات-العربية-الخصائص-المنطقية-و-rtl/
- Published: 2026-10-30T12:59:11.000Z
- Updated: 2026-10-30T19:46:05.000Z
- Description: كيف تكتب ملف CSS واحداً يعمل بالعربية والإنجليزية، ولماذا تخطئ margin-left في الصفحة العربية، مع قياسات حقيقية من Chrome.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, تطوير الواجهات والجوال Frontend & Mobile

لنفرض أنك في فريق صغير يبني متجراً إلكترونياً لسماعات الرأس وأجهزة الصوت، وكانت الواجهة الأولى بالإنجليزية فقط لأن أول العملاء كانوا من الخارج، ثم قررت الإدارة إطلاق النسخة العربية خلال أسبوعين. المطور المسؤول أضاف `dir="rtl"` إلى الصفحة، وترجم النصوص، ورأى أن القائمة العلوية انقلبت وحدها فاطمأن، ثم وصل تقرير المصممة في اليوم التالي بعشرين ملاحظة: الأيقونة ملتصقة بالنص في زر «أضف إلى السلة»، والخط البرتقالي الذي يميز البطاقة ما زال على اليسار، والصورة المصغرة في الجهة الخطأ، وآخر سطر في الوصف معلق على اليسار، وسهم «المنتج التالي» يشير إلى الخلف، والعنوان الكبير مقصوص من الأسفل في الجوال، والنص يظهر لثانية بخط النظام ثم يقفز إلى الخط العربي. وكل هذه الملاحظات لها سبب واحد تقريباً، وهو أن ملف CSS كتب بلغة اليسار واليمين، بينما المتصفح يفكر في بداية السطر ونهايته.

وهذا المقال من مرحلة [هندسة الواجهات وتطبيقات الويب Frontend](https://arabroot.io/start/#stage-web-frontend)، وسوف نأخذ فيه نفس البطاقة ونفس الصفحة ونعرضها في Chrome باتجاهين، ونقيس كل خطأ بالبكسل قبل إصلاحه وبعده. أما ترتيب الكلمات داخل السطر نفسه، أي خوارزمية الاتجاه الثنائي BiDi وعزل الأسماء والأرقام وتنسيق التواريخ، فله مقال خاص في نفس المرحلة، وهنا نركز على التخطيط Layout والخطوط. وسوف نناقش في هذا المقال ما يلي:

- كيف يضع المتصفح العناصر: نموذج الصندوق والتدفق العادي، ومحورا السطر والكتل اللذان تبنى عليهما الخصائص المنطقية.
- ماذا تفعل الخصائص الفيزيائية مثل `margin-left` و`border-left` و`float: left` في صفحة عربية، وكيف تستبدلها بالخصائص المنطقية Logical Properties.
- أين تضع الاتجاه: السمة `dir` في HTML أم الخاصية `direction` في CSS، ولماذا يهم الفرق.
- Flexbox وGrid في الصفحة العربية، وما يحدث إذا قلبت الترتيب يدوياً بـ `row-reverse`، والتصميم المتجاوب بـ `auto-fit` و`minmax`.
- الأيقونات: أيها تقلب وأيها تبقى كما هي.
- الخطوط العربية: سلسلة البدائل Fallback، وارتفاع السطر، و`letter-spacing`، و`font-display`.
- تجربة عملية كاملة في Chrome مع القياسات الحقيقية، ثم الأخطاء الشائعة وقائمة مراجعة قبل الإطلاق.

## كيف يضع المتصفح العناصر في الصفحة؟

قبل أن نتحدث عن اليمين واليسار نحتاج إلى فكرتين بسيطتين، لأن كل ما يأتي بعدهما مبني عليهما. الفكرة الأولى هي نموذج الصندوق Box Model، فكل عنصر في الصفحة يرسمه المتصفح كصندوق مستطيل له أربع طبقات من الداخل إلى الخارج: المحتوى Content، ثم الحشو Padding، ثم الحد Border، ثم الهامش Margin، وكل طبقة من الثلاث الأخيرة لها أربعة جوانب يمكن أن تختلف قيمها. وقد اعتدنا منذ بدايات CSS أن نسمي هذه الجوانب بأسماء الشاشة: الأعلى top والأسفل bottom واليسار left واليمين right، فنكتب `padding-left` و`border-right`، وهذه هي الخصائص الفيزيائية Physical Properties، أي أنها مربوطة بجوانب الشاشة مهما كانت لغة الصفحة. ولا تنس أن `box-sizing: border-box` يجعل العرض الذي تكتبه يشمل الحشو والحد، وهو ما نستخدمه في كل أمثلة المقال لأنه يجعل الحساب أسهل، وتفاصيل النموذج كاملة في [شرح MDN لنموذج الصندوق](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS%5Fbox%5Fmodel/Introduction%5Fto%5Fthe%5FCSS%5Fbox%5Fmodel?ref=arabroot.io).

والفكرة الثانية هي التدفق العادي Normal Flow، وهو الطريقة التي يرتب بها المتصفح العناصر إذا لم تطلب منه شيئاً خاصاً، فالعناصر الكتلية Block مثل `div` و`p` و`h3` تأتي واحدة تحت الأخرى، وكل منها يأخذ عرض الأب كاملاً، والعناصر السطرية Inline مثل `span` و`a` تأتي متتالية داخل السطر كما تأتي الكلمات، وإذا امتلأ السطر انتقلت إلى السطر التالي. ولاحظ أنني لم أقل إن العناصر السطرية تأتي من اليسار إلى اليمين، وهذا مقصود، فالتدفق العادي في [شرح MDN للتدفق](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS%5Fdisplay/Flow%5Flayout?ref=arabroot.io) يبدأ من بداية السطر، وبداية السطر تحددها لغة الصفحة: في الإنجليزية على اليسار، وفي العربية على اليمين. ولذلك عندما أضاف مطور المتجر `dir="rtl"` انقلب النص والقائمة وحدهما دون أي سطر CSS، لأنهما يتبعان التدفق، ولم ينقلب ما كتب بأسماء الشاشة.

وإذا أردت أن تعرف ما يحدث داخل المتصفح بعد ذلك، أي كيف تتحول هذه الصناديق إلى شجرة ثم تحسب أماكنها Layout ثم ترسم Paint، فهذا موضوع مقال المتصفح من الداخل في نفس المرحلة، وهنا يكفينا أن نعرف أن المتصفح يحسب مكان كل صندوق من اتجاه الكتابة أولاً، ثم يحول النتيجة إلى إحداثيات على الشاشة.

## اتجاه السطر واتجاه الكتل: inline وblock

المواصفة التي تعرف هذا كله اسمها [CSS Writing Modes](https://www.w3.org/TR/css-writing-modes-4/?ref=arabroot.io)، وهي تتعامل مع الصفحة بمحورين بدلاً من أربع جهات. المحور الأول هو محور السطر Inline Axis، وهو الاتجاه الذي تسير فيه الكلمات داخل السطر، والمحور الثاني هو محور الكتل Block Axis، وهو الاتجاه الذي تتراكم فيه الفقرات والعناوين واحدة بعد الأخرى. وفي العربية والإنجليزية محور الكتل من الأعلى إلى الأسفل في الحالتين، أما محور السطر فمن اليمين إلى اليسار في العربية ومن اليسار إلى اليمين في الإنجليزية، وفي اليابانية العمودية مثلاً يصبح محور السطر من الأعلى إلى الأسفل ومحور الكتل من اليمين إلى اليسار، ولهذا اختارت المواصفة أسماء لا تذكر الشاشة أصلاً.

ولكل محور بداية ونهاية، فتصبح الجهات الأربع: بداية السطر inline-start ونهايته inline-end، وبداية الكتل block-start ونهايتها block-end. والصورة التالية تبين نفس الصندوق في صفحة إنجليزية وصفحة عربية:

![مربعان متجاوران، الأيمن في صفحة إنجليزية والأيسر في صفحة عربية، حول كل منهما أسماء الجهات الفيزيائية top وbottom وleft وright بالرمادي والمنطقية block-start وblock-end وinline-start وinline-end بالبرتقالي، وخط برتقالي سميك على جهة inline-start يقع على اليسار في الإنجليزية وعلى اليمين في العربية، وسهم أزرق لاتجاه السطر وسهم أخضر لاتجاه الكتل](https://arabroot.io/content/images/2026/10/css-layout-rtl-01-logical-map.webp)

الجهة اليسرى تبقى يساراً في الصفحتين، أما بداية السطر فتنتقل مع لغة الصفحة

لاحظ في الصورة أن block-start هو الأعلى في الحالتين، ولذلك لن تجد فرقاً بين `margin-top` و`margin-block-start` في المواقع العربية والإنجليزية، والفرق كله في المحور الأفقي، وهذا يعني عملياً أن مشكلة الواجهة العربية محصورة في كل خاصية فيها كلمة left أو right، وهي التي سوف نتتبعها في البطاقة التالية.

## ماذا يحدث للبطاقة عندما تقلب الصفحة؟

لنأخذ بطاقة المنتج من المتجر كما كتبها الفريق للنسخة الإنجليزية، وفيها ما تجده في أي بطاقة حقيقية: خط برتقالي على جهة البداية يميز المنتجات المختارة، وزر إغلاق في الزاوية، وصورة مصغرة يلتف حولها النص، وزر شراء فيه أيقونة تفصلها عن النص مسافة، ورابط «المنتج التالي» مع سهم، وزران متلاصقان للتصفية زواياهما الخارجية مستديرة. والـ CSS الخاص بها كتب بالخصائص الفيزيائية كما يلي:

```css
.card { position: relative; border-left: 4px solid #E95420; padding: 16px 48px 16px 16px; text-align: left; }
.card .close { position: absolute; top: 10px; right: 10px; }
.card .thumb { float: left; margin-right: 14px; }
.btn .icon { margin-right: 8px; }
.seg button:first-child { border-top-left-radius: 10px; border-bottom-left-radius: 10px; }
.seg button:last-child { border-top-right-radius: 10px; border-bottom-right-radius: 10px; }
```

ثم عرضنا نفس البطاقة في Chrome مرتين، مرة في صفحة إنجليزية ومرة في صفحة عربية، والفرق الوحيد بين الصفحتين هو السمة `dir` والنصوص المترجمة، والصورة التالية لقطة حقيقية من المتصفح:

![لقطة من Chrome لبطاقة المنتج بالخصائص الفيزيائية: على اليمين النسخة العربية والخط البرتقالي ما زال على يسارها والصورة المصغرة على اليسار وآخر سطر من الوصف على اليسار والأيقونة ملتصقة بنص زر أضف إلى السلة وسهم المنتج التالي يشير إلى اليمين، وعلى اليسار النسخة الإنجليزية سليمة](https://arabroot.io/content/images/2026/10/css-layout-rtl-02-physical.webp)

نفس CSS في الصفحتين: الإنجليزية سليمة، والعربية فيها ستة أخطاء دون أي رسالة خطأ

ولم نكتف بالنظر، فقد قسنا كل عنصر بالدالتين `getBoundingClientRect` و`getComputedStyle` كما سوف ترى في [التجربة العملية](#lab)، والنتيجة في الصفحة العربية كما يلي:

- **الأيقونة ملتصقة بالنص:** المسافة بين الأيقونة والنص في الإنجليزية 8 بكسلات، وفي العربية صفر، والسبب أن الأيقونة أصبحت على يمين النص لأنها أول عنصر في السطر، أما الهامش فبقي على يمينها، أي على الجهة الخارجية للزر بدلاً من الجهة التي تواجه النص.
- **الخط البرتقالي على الجهة الخطأ:** الحد الأيسر بقي 4 بكسلات والأيمن صفراً، والمصمم أراده على بداية البطاقة، وبداية البطاقة العربية على اليمين.
- **الصورة المصغرة على اليسار:** لأن `float: left` يضعها على اليسار دائماً، فيبدأ القارئ العربي بالنص ثم يجد الصورة في نهاية السطر.
- **آخر سطر في الوصف معلق على اليسار:** الخاصية `text-align` قيمتها `left`، فالأسطر الممتلئة تبدو سليمة، والسطر الأخير القصير يلتصق باليسار، وهذا من أكثر الأخطاء التي تفضح أن الصفحة مترجمة.
- **الزوايا المستديرة في الداخل:** زر «الكل» أصبح أول زر على اليمين، ولكن زاويته المستديرة ما زالت على يساره، أي في المنتصف بين الزرين.
- **السهم يشير إلى الخلف:** سهم «المنتج التالي» يشير إلى اليمين، وفي الصفحة العربية التالي على اليسار.

وقد يتساءل البعض: وماذا عن زر الإغلاق؟ القياس قال إنه بقي على اليمين ولم يتداخل مع العنوان، لأن الحشو الأيمن 48 بكسلاً بقي أيضاً على اليمين، فالخطأ هنا متسق مع نفسه، ولكن الزر أصبح في بداية القراءة، أي أول ما تقع عليه عين القارئ العربي، والمصمم وضعه في نهاية السطر حتى لا يكون أول شيء يراه المستخدم. وهذه ملاحظة مهمة: الخصائص الفيزيائية لا تفشل دائماً بشكل واضح، وأحياناً تعطيك صفحة تبدو سليمة لمن لا يقرأ العربية، وهذا هو السبب في أن أخطاء الواجهات العربية تبقى أشهراً في الإنتاج.

والحل البسيط الذي يلجأ إليه كثير من الفرق هو ملف CSS ثان للعربية يقلب كل left إلى right، أو أداة تفعل ذلك آلياً عند البناء Build، وهذا الحل يعمل في البداية، ولكنه يضاعف عدد الملفات التي يجب أن تبقى متطابقة، وكل تعديل في الملف الإنجليزي يجب أن ينعكس في العربي، وأي مكون جديد يكتبه مطور لا يعرف القاعدة يعود بنفس الأخطاء. والحل الأنسب هو أن يكتب CSS مرة واحدة بلغة البداية والنهاية، وهو ما يأتي في القسم التالي.

## الخصائص المنطقية Logical Properties: margin-inline-start وأخواتها

الخصائص المنطقية تعرفها مواصفة [CSS Logical Properties](https://www.w3.org/TR/css-logical-1/?ref=arabroot.io)، وهي نسخة من كل خاصية فيزيائية تقريباً ولكن بأسماء المحورين بدلاً من جهات الشاشة، فبدلاً من أن تقول للمتصفح «ضع الهامش على يمين الأيقونة» تقول له «ضع الهامش في نهاية الأيقونة»، ويقرر المتصفح أين النهاية حسب لغة الصفحة. والجدول التالي يبين الخصائص التي تحتاجها في أغلب المشاريع:

| الفيزيائية                       | المنطقية                                                | في الصفحة العربية تساوي                      |
| -------------------------------- | ------------------------------------------------------- | -------------------------------------------- |
| margin-left                      | margin-inline-start                                     | margin-right                                 |
| margin-right                     | margin-inline-end                                       | margin-left                                  |
| padding-left وpadding-right معاً | padding-inline: 16px 48px                               | البداية 16 على اليمين والنهاية 48 على اليسار |
| border-left                      | border-inline-start                                     | border-right                                 |
| left وright وtop                 | inset-inline-start وinset-inline-end وinset-block-start | right وleft وtop                             |
| border-top-left-radius           | border-start-start-radius                               | border-top-right-radius                      |
| float: left                      | float: inline-start                                     | float: right                                 |
| text-align: left                 | text-align: start                                       | text-align: right                            |
| width وheight                    | inline-size وblock-size                                 | نفس القيم في العربية والإنجليزية             |

وبهذا الجدول أعاد الفريق كتابة البطاقة، ولاحظ أن عدد الأسطر لم يتغير، وأن الملف واحد للغتين:

```css
.card { position: relative; border-inline-start: 4px solid #E95420; padding-block: 16px; padding-inline: 16px 48px; text-align: start; }
.card .close { position: absolute; inset-block-start: 10px; inset-inline-end: 10px; }
.card .thumb { float: inline-start; margin-inline-end: 14px; }
.btn .icon { margin-inline-end: 8px; }
.seg button:first-child { border-start-start-radius: 10px; border-end-start-radius: 10px; }
.seg button:last-child { border-start-end-radius: 10px; border-end-end-radius: 10px; }
.chev:dir(rtl) { transform: scaleX(-1); }
```

والصورة التالية نفس البطاقة بعد الإصلاح، في نفس النافذة ونفس المتصفح:

![لقطة من Chrome للبطاقة بالخصائص المنطقية: في النسخة العربية الخط البرتقالي على اليمين والصورة المصغرة على اليمين وزر الإغلاق على اليسار والوصف محاذى لليمين والأيقونة بعيدة عن النص وسهم المنتج التالي يشير إلى اليسار، والنسخة الإنجليزية كما هي](https://arabroot.io/content/images/2026/10/css-layout-rtl-03-logical.webp)

نفس الملف للغتين: البطاقة العربية صورة مرآة صحيحة للإنجليزية

والقياس بعد الإصلاح يؤكد ما في الصورة: المسافة بين الأيقونة والنص 8 بكسلات في اللغتين، والهامش انتقل وحده إلى يسار الأيقونة في العربية، والحد البرتقالي أصبح 4 بكسلات على اليمين، والحشو الواسع 48 بكسلاً انتقل إلى اليسار حيث أصبح زر الإغلاق، والصورة المصغرة على اليمين، والزاوية المستديرة للزر الأول انتقلت إلى يمينه. ولاحظ أن الصفحة الإنجليزية أعطت نفس الأرقام التي أعطتها بالخصائص الفيزيائية بالضبط، وهذا هو الضمان الذي تحتاجه عند الترحيل Migration: الخصائص المنطقية لا تغير شيئاً في الصفحة الإنجليزية، فيمكنك ترحيل الملف على مراحل دون خوف على النسخة الحالية.

### الاختصارات: padding-inline وinset وزوايا الحدود

أكثر ما يربك عند البداية هو ترتيب القيم في الاختصارات، فالخاصية `padding-inline: 16px 48px` تعني البداية ثم النهاية، أي 16 في البداية و48 في النهاية، وإذا كتبت قيمة واحدة طبقت على الجهتين، و`padding-block` بنفس الطريقة للأعلى والأسفل. أما الاختصار القديم `padding: 16px 48px 16px 16px` فيبقى فيزيائياً، أي أعلى ثم يمين ثم أسفل ثم يسار، ولا توجد طريقة في CSS اليوم لتجعل هذا الاختصار ذا الأربع قيم منطقياً، ولذلك نقسمه إلى سطرين: `padding-block` و`padding-inline`. والخاصية `margin` مثلها تماماً.

وفي العناصر ذات الموضع المطلق Absolute Position نستخدم `inset-inline-start` و`inset-inline-end` بدلاً من `left` و`right`، كما في [صفحة MDN للخاصية inset-inline-end](https://developer.mozilla.org/en-US/docs/Web/CSS/inset-inline-end?ref=arabroot.io)، وهي التي نقلت زر الإغلاق إلى اليسار في الصفحة العربية. أما الزوايا فأسماؤها أطول قليلاً لأن كل زاوية تقع بين محورين، فالاسم يذكر جهة محور الكتل أولاً ثم جهة محور السطر: `border-start-start-radius` هي زاوية بداية الكتل وبداية السطر، أي الأعلى يميناً في العربية، و`border-end-start-radius` هي الأسفل يميناً، والتفاصيل في [صفحة MDN للخاصية border-start-start-radius](https://developer.mozilla.org/en-US/docs/Web/CSS/border-start-start-radius?ref=arabroot.io). ولتتذكرها بسهولة: الكلمة الأولى للأعلى والأسفل، والثانية لبداية السطر ونهايته.

### float وtext-align: القيم المنطقية

بعض الخصائص لم تحتج إلى اسم جديد وإنما إلى قيم جديدة، فالخاصية `float` أصبحت تقبل `inline-start` و`inline-end` كما في [صفحة MDN للخاصية float](https://developer.mozilla.org/en-US/docs/Web/CSS/float?ref=arabroot.io)، وكذلك `clear`. والخاصية `text-align` تقبل `start` و`end` منذ زمن بعيد، بل إن قيمتها الافتراضية في [المتصفحات الحديثة](https://developer.mozilla.org/en-US/docs/Web/CSS/text-align?ref=arabroot.io) هي `start`، وهذا يعني أن أفضل إصلاح لكثير من الصفحات هو حذف `text-align: left` وليس استبداله، لأن المطور كتبه أصلاً ليؤكد القيمة الافتراضية في الصفحة الإنجليزية. ولاحظ في القياس أن `getComputedStyle` أعاد القيمة `start` كما هي ولم يحولها إلى `right`، فإذا كان عندك اختبار آلي يتحقق من المحاذاة فاجعله يتحقق من موضع النص بالبكسل وليس من قيمة الخاصية.

## أين تضع الاتجاه: dir في HTML أم direction في CSS؟

هناك طريقتان لتقول للمتصفح إن الصفحة عربية من اليمين إلى اليسار: السمة `dir="rtl"` على عنصر `html`، أو الخاصية `direction: rtl` في CSS. والطريقتان تعطيان نفس النتيجة في التخطيط في أغلب الحالات، ولكن مجموعة التدويل في W3C تقول صراحة في [صفحة الإجابة عن سمة dir](https://www.w3.org/International/questions/qa-html-dir?ref=arabroot.io): إذا كان اتجاه المستند كله من اليمين إلى اليسار فضع `dir="rtl"` على عنصر html، ولا تستخدم CSS أبداً لتحديد الاتجاه الأساسي، واستخدم القيم المنطقية start وend في الخصائص. وكذلك تقول [صفحة MDN للخاصية direction](https://developer.mozilla.org/en-US/docs/Web/CSS/direction?ref=arabroot.io) إن الاتجاه يحدد عادة داخل المستند بسمة dir وليس بهذه الخاصية مباشرة.

والسبب الأول أن الاتجاه جزء من معنى المحتوى وليس من شكله، فالنص العربي يبقى عربياً إذا فشل تحميل ملف CSS، أو إذا قرأته أداة لا تفهم CSS مثل قارئ الشاشة أو محرك البحث أو برنامج البريد. والسبب الثاني ظهر لنا في التجربة نفسها، فقد صنعنا صفحة فيها `body { direction: rtl; }` دون أي سمة `dir`، ووضعنا فيها نفس سهم «المنتج التالي» مع القاعدة `.chev:dir(rtl) { transform: scaleX(-1); }`، والنتيجة كما يلي:

```text
-- /nodir: html dir="", body direction rtl, chevron matches :dir(rtl) false, transform none
-- /card?dir=rtl&css=logical: html dir="rtl", body direction rtl, chevron matches :dir(rtl) true, transform matrix(-1, 0, 0, 1, 0, 0)
```

الصفحة الأولى اتجاهها في CSS من اليمين إلى اليسار، والنص على اليمين، ولكن المحدد `:dir(rtl)` لم يطابق السهم، فبقي السهم دون قلب، والسبب أن [المحدد :dir](https://developer.mozilla.org/en-US/docs/Web/CSS/:dir?ref=arabroot.io) يقرأ الاتجاه من سمة `dir` في HTML وليس من الخاصية `direction` في CSS. أما الصفحة الثانية التي فيها `dir="rtl"` فطابق المحدد وظهرت مصفوفة القلب `matrix(-1, 0, 0, 1, 0, 0)`. أي أن من يضع الاتجاه في CSS يخسر بصمت كل قاعدة مبنية على `:dir()`، ولن يرى أي خطأ إلا عندما ينتبه أحد للسهم.

وفي الصفحة العربية نفسها تحتاج أحياناً إلى عنصر اتجاهه إنجليزي، مثل حقل البريد الإلكتروني ورقم الهاتف وكتلة الكود، وهنا تضع `dir="ltr"` على ذلك العنصر وحده، وأما النصوص التي تأتي من المستخدم ولا تعرف لغتها مسبقاً، مثل اسم المنتج الذي يكتبه البائع، فتحتاج إلى `dir="auto"` أو عنصر `bdi`، وهذه هي منطقة خوارزمية الاتجاه الثنائي التي شرحناها في مقالها الخاص، وقد ذكرنا طرفاً منها أيضاً في [أسئلة مقابلة مطور Frontend](https://arabroot.io/articles/%D8%A7%D8%B3%D8%A6%D9%84%D8%A9-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D9%85%D8%B7%D9%88%D8%B1-frontend-%D9%85%D9%86-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D8%A8%D9%8A%D8%B1/#rtl). ولا تنس السمة `lang="ar"` بجانب `dir`، فهي التي يعتمد عليها المتصفح في اختيار الخط المناسب وفي النطق في قارئ الشاشة، وغيابها يجعل المتصفح يخمن.

## Flexbox: المحور الرئيسي Main Axis يتبع الاتجاه

الخبر الجيد أن Flexbox وGrid كتبا من البداية بلغة المحاور، فلا تحتاج فيهما إلى خصائص منطقية جديدة. في Flexbox كل حاوية لها محور رئيسي Main Axis تصطف عليه العناصر، ومحور عرضي Cross Axis عمودي عليه، وعند `flex-direction: row` يكون المحور الرئيسي هو محور السطر، فيبدأ من بداية السطر، أي من اليمين في الصفحة العربية، كما تشرح [صفحة MDN لمفاهيم Flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS%5Fflexible%5Fbox%5Flayout/Basic%5Fconcepts%5Fof%5Fflexbox?ref=arabroot.io). وكذلك الخصائص `justify-content: flex-start` و`align-items` و`gap`، فكلها مبنية على البداية والنهاية.

وفي المتجر شريط علوي فيه الشعار ثم روابط التصنيفات ثم زر السلة، والسلة مدفوعة إلى نهاية الشريط بالقاعدة `margin-inline-start: auto`، وهي طريقة شائعة في Flexbox لدفع عنصر إلى الطرف الآخر، والصورة التالية تبين ما حدث للمحور في كل حالة، مع الإحداثيات التي قسناها في نافذة عرضها 1000 بكسل:

![ثلاثة صفوف من المربعات المرقمة 1 و2 و3: في الصفحة الإنجليزية مع row تبدأ من اليسار، وفي العربية مع row تبدأ من اليمين، وفي العربية مع row-reverse تعود لتبدأ من اليسار بسهم أحمر، وتحتها صف Grid فيه aside على اليمين وmain على اليسار في الصفحة العربية](https://arabroot.io/content/images/2026/10/css-layout-rtl-04-flex-axes.webp)

في Flexbox وGrid لا تكتب شيئاً للعربية: الترتيب ينقلب وحده مع dir

والصورة التالية لقطة حقيقية من Chrome للصفحة العربية كاملة، وفيها الشريط العلوي وشبكة المنتجات والعمود الجانبي:

![لقطة من Chrome للصفحة العربية: شريط علوي داكن فيه متجر الصوت والسماعات والعروض على اليمين وزر السلة البرتقالي على اليسار، وتحته عمود التصنيفات على اليمين وشبكة من خمسة منتجات تبدأ من اليمين بثلاثة أعمدة](https://arabroot.io/content/images/2026/10/css-layout-rtl-05-layout-rtl.webp)

الشريط والعمود الجانبي والشبكة انقلبت دون أي سطر CSS خاص بالعربية

### ماذا يحدث إذا قلبت الترتيب يدوياً بـ row-reverse؟

هذا خطأ شائع في المشاريع التي تترجم واجهتها بعد بنائها، فالمطور الذي تعود على الخصائص الفيزيائية يظن أن الصفحة العربية تحتاج إلى قلب كل شيء يدوياً، فيكتب في ملف العربية `flex-direction: row-reverse` للشريط العلوي، والنتيجة أن الصف ينقلب مرتين: مرة بسبب `dir="rtl"` ومرة بسبب `row-reverse`، فيعود الترتيب الإنجليزي. وقد قسنا ذلك في التجربة فكان الشعار عند x=36، أي على اليسار، والسلة عند x=268 بجانب الروابط بدلاً من الطرف الآخر:

![لقطة من Chrome للشريط العلوي في الصفحة العربية مع row-reverse: متجر الصوت على اليسار ثم العروض والسماعات ثم زر السلة في المنتصف تقريباً، والجهة اليمنى من الشريط فارغة](https://arabroot.io/content/images/2026/10/css-layout-rtl-06-row-reverse.webp)

row-reverse فوق dir="rtl" أعاد الترتيب الإنجليزي، وترك الجهة اليمنى فارغة

وسبب مكان السلة الغريب أن `row-reverse` يجعل بداية المحور الرئيسي على اليسار، ولكن `margin-inline-start: auto` ما زال مربوطاً بالسطر وليس بالمحور، وبداية السطر العربي على اليمين، فوضع المتصفح الهامش التلقائي على يمين السلة، فدفعها نحو بقية العناصر بدلاً من دفعها بعيداً عنها. وهذا مثال واضح على أن خلط القلب اليدوي مع القلب التلقائي يعطيك نتائج لا يتوقعها أحد، والقاعدة: لا تستخدم `row-reverse` و`order` لتغيير الاتجاه، والسبب أنهما يغيران الترتيب البصري فقط ويتركان ترتيب الكود كما هو، فيصبح ترتيب التنقل بزر Tab مختلفاً عن ترتيب العين، وهذا يضر مستخدم لوحة المفاتيح وقارئ الشاشة كما شرحنا في [مقال سهولة الوصول](https://arabroot.io/articles/%D8%B3%D9%87%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-accessibility-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%85%D9%88%D9%82%D8%B9%D8%A7-%D9%8A%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%87-%D8%A7%D9%84%D8%AC%D9%85%D9%8A%D8%B9/#tabs-rtl). استخدمهما فقط عندما يكون الترتيب المختلف مقصوداً في اللغتين معاً.

## Grid: المسارات Tracks ووحدة fr والمناطق Areas

في Grid تقسم الحاوية إلى مسارات Tracks، أي أعمدة وصفوف، ثم تضع العناصر فيها، وتشرح [صفحة MDN لمفاهيم Grid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS%5Fgrid%5Flayout/Basic%5Fconcepts%5Fof%5Fgrid%5Flayout?ref=arabroot.io) كيف تحسب المسارات. والقاعدة التي تهمنا هنا أن العمود الأول هو عمود البداية، فالقاعدة `grid-template-columns: 200px 1fr` تعني عموداً بعرض 200 بكسل في بداية السطر، ثم عموداً يأخذ ما بقي من المساحة، ووحدة `fr` تعني جزءاً من المساحة الحرة بعد طرح الأعمدة الثابتة والفجوات. وفي القياس كان العمود الجانبي في الصفحة الإنجليزية عند x=20 وفي العربية عند x=780، أي أنه انتقل من اليسار إلى اليمين دون أن نكتب شيئاً.

والأجمل من ترقيم الأعمدة هو المناطق المسماة Named Areas، فبدلاً من أن تقول «العمود الأول» تعطي كل منطقة اسماً، وترسم الصفحة كأنها خريطة نصية:

```css
.page { display: grid; grid-template-columns: 200px 1fr; grid-template-areas: "side main"; gap: 16px; }
aside { grid-area: side; }
main { grid-area: main; }
@media (max-width: 600px) {
  .page { grid-template-columns: 1fr; grid-template-areas: "main" "side"; }
}
```

لاحظ أن النص `"side main"` يقرأ من بداية السطر، فالمنطقة side في البداية، وهي على اليمين في العربية. وقد يتساءل البعض: إذا كانت الخريطة تكتب بأحرف إنجليزية من اليسار إلى اليمين، فلماذا لا يضعها المتصفح على اليسار؟ والإجابة أن [الخاصية grid-template-areas](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas?ref=arabroot.io) تعرف الأعمدة بترتيبها، والترتيب يبدأ من بداية السطر مثل أي شيء آخر في Grid، فالخريطة تصف ترتيب الأعمدة وليس مكانها على الشاشة. ولاحظ أيضاً أن الخصائص `justify-items` و`justify-self` في Grid تعمل على محور السطر، فالقيمة `start` فيها تعني اليمين في الصفحة العربية.

### repeat(auto-fit, minmax()) والتصميم المتجاوب Responsive

شبكة المنتجات في المتجر لا تحدد عدد الأعمدة أصلاً، وإنما تقول للمتصفح: ضع أكبر عدد من الأعمدة التي لا يقل عرض كل منها عن 180 بكسلاً، ووزع الباقي عليها بالتساوي:

```css
.list { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
```

والدالة [repeat](https://developer.mozilla.org/en-US/docs/Web/CSS/repeat?ref=arabroot.io) مع `auto-fit` تحسب عدد الأعمدة من العرض المتاح، و`minmax(180px, 1fr)` تقول إن العمود لا يقل عن 180 ولا يزيد عن جزء من المساحة الحرة. وفي نافذة عرضها 1000 بكسل حسب Chrome ثلاثة أعمدة عرض كل منها 240 بكسلاً، وفي نافذة عرضها 390 بكسلاً، وهو عرض جوال شائع، أصبح عموداً واحداً بعرض 350 بكسلاً، ومع قاعدة `@media` انتقلت المنطقة main فوق side، والقياس أكد ذلك بالقيمة `main above aside: true`:

![لقطة من Chrome للصفحة العربية بعرض 390 بكسلاً: الشريط العلوي فيه متجر الصوت على اليمين والسلة على اليسار والروابط مخفية، ثم المنتجات من 1 إلى 5 في عمود واحد، ثم التصنيفات في الأسفل](https://arabroot.io/content/images/2026/10/css-layout-rtl-07-mobile-rtl.webp)

في الجوال: عمود واحد، والمنتجات قبل التصنيفات، والترتيب العربي محفوظ

والدرس العربي في التصميم المتجاوب أن النص العربي يحتاج غالباً إلى عرض مختلف عن الإنجليزي، فالكلمات العربية أقصر في بعض الحالات وأطول في غيرها، والحروف تحتاج إلى ارتفاع أكبر كما سوف نرى، ولذلك لا تثبت عرض الأزرار والبطاقات بالبكسل بناءً على النص الإنجليزي، واجعل المحتوى يحدد العرض بـ `min-width` و`minmax`، واختبر كل نقطة توقف Breakpoint باللغتين. وفي المتجر مثلاً كان زر «أضف إلى السلة» أعرض قليلاً من «Add to cart» في اللقطات السابقة، وعنوان المنتج احتاج إلى سطرين في اللغتين ولكن انقسم عند كلمات مختلفة، فلا توجد قاعدة ثابتة غير الاختبار.

## ما الذي لا ينقلب وحده؟

بعد ترحيل الخصائص المنطقية وFlexbox وGrid يبقى عدد من الأشياء التي لا تعرف شيئاً عن اتجاه الصفحة، لأنها مبنية على إحداثيات الشاشة بطبيعتها، وهي التي تظهر منها أخطاء المرحلة الثانية:

- **الإزاحة بـ `transform`:** الدالة `translateX(100%)` تحرك العنصر إلى اليمين دائماً، فالقائمة الجانبية التي تنزلق من اليسار في الإنجليزية تنزلق من الجهة الخطأ في العربية. والحل أن تضرب الإزاحة في متغير يتغير مع الاتجاه، مثل `--dir: 1` في الإنجليزية و`-1` تحت `:dir(rtl)`، ثم `translateX(calc(100% * var(--dir)))`.
- **ظل الصندوق `box-shadow`:** الإزاحة الأفقية فيه فيزيائية، فالظل الذي يقع على يمين البطاقة يبقى على يمينها، وهذا مقبول غالباً لأن الضوء في الواجهة لا علاقة له بلغة القراءة.
- **موضع الخلفية `background-position`:** صورة الخلفية في `left center` تبقى على اليسار، فإذا كانت أيقونة داخل حقل البحث فانقلها بـ `:dir(rtl)` أو استبدلها بعنصر حقيقي داخل Flexbox.
- **الحركات Animations والقيم في JavaScript:** أي كود يحسب `style.left` أو يقرأ `event.clientX` ليعرف الاتجاه يجب أن يقرأ اتجاه الصفحة من `document.dir` أو من `getComputedStyle(el).direction`، وإلا فسوف يعمل في لغة واحدة فقط.

## الأيقونات: أيها تقلب وأيها تبقى؟

في تقرير المصممة كانت هناك ملاحظة عن سهم «المنتج التالي»، وملاحظة معاكسة لم تكن متوقعة: أحد المطورين قلب كل أيقونات الصفحة بـ `scaleX(-1)` في النسخة العربية، فأصبحت عدسة البحث مقلوبة وعلامة الصح مقلوبة وأيقونة الساعة تدور عكس عقارب الساعة. والقاعدة التي تحكم ذلك ليست في CSS وإنما في معنى الأيقونة، وأوضح مرجع لها هو [إرشادات Apple للواجهات من اليمين إلى اليسار](https://developer.apple.com/design/human-interface-guidelines/right-to-left?ref=arabroot.io)، وخلاصتها في الصورة التالية:

![جدول من عمودين: تقلب، وفيه أسهم السابق والتالي وسهم القائمة الفرعية وشريط التقدم وأيقونة محاذاة النص، ولا تقلب، وفيه العدسة والتنزيل وعلامة الصح والرقم 2026، وبجانب كل أيقونة شكلها في الصفحة الإنجليزية ثم العربية](https://arabroot.io/content/images/2026/10/css-layout-rtl-08-icons.webp)

تقلب الأيقونة إذا كان معناها مرتبطاً باتجاه القراءة، وتبقى إذا كانت شيئاً حقيقياً أو تشير إلى اتجاه حقيقي

ونفصلها في نقاط:

- **تقلب أيقونات الحركة للأمام والخلف:** السابق والتالي وسهم الرجوع وسهم القائمة الفرعية، لأن «التالي» في العربية على اليسار.
- **تقلب أيقونات النص والقراءة:** محاذاة الفقرة والمسافة البادئة Indent والقائمة المرقمة، لأنها صورة لنص يقرأ من اتجاه معين.
- **تقلب أدوات التقدم:** شريط التقدم Progress والمنزلق Slider وأداة التقييم بالنجوم تمتلئ من بداية السطر، وتقول Apple إن الأرقام فيها ينقلب ترتيبها ولكن لا تقلب الأرقام نفسها.
- **لا تقلب الأشياء الحقيقية:** العدسة والساعة والكاميرا والهاتف، فالساعة تدور بنفس الاتجاه في كل لغة كما تقول Apple.
- **لا تقلب الاتجاه الحقيقي:** سهم التنزيل يشير إلى الأسفل في كل اللغات، وسهم يشير إلى منطقة معينة في الشاشة يبقى يشير إليها.
- **لا تقلب الشعارات والعلامات العالمية:** علامة الصح وشعارات الشركات، وتنبه Apple إلى أن الشعار المقلوب يربك المستخدم وقد تكون له تبعات قانونية.

وفي الكود نضع على الأيقونات التي تقلب فقط صنفاً خاصاً، ونقلبها بالمحدد `:dir(rtl)` كما في البطاقة: `.chev:dir(rtl) { transform: scaleX(-1); }`، وفي القياس كانت قيمة `transform` لسهم «المنتج التالي» في الصفحة العربية `matrix(-1, 0, 0, 1, 0, 0)`، وفي الإنجليزية `none`، بينما بقيت عدسة البحث كما هي في الحالتين. ولاحظ أن القلب بـ `scaleX(-1)` يقلب الأيقونة حول مركزها، فإذا كانت الأيقونة غير متماثلة في المساحة الفارغة حولها فقد تحتاج إلى نسخة مرسومة خاصة للعربية، وهذا ما تقصده Apple عندما تقول إن الأيقونة المعقدة تحتاج إلى نظر في مكوناتها قبل قلبها.

## الخطوط العربية: الاختيار وسلسلة البدائل Fallback

بعد أن استقام التخطيط بقيت ملاحظات المصممة عن الخط، وأولها أن عنوان المنتج في بعض الصفحات يظهر بخط يختلف عن بقية الصفحة. والسبب كان في سلسلة الخطوط `font-family`، فالفريق اختار للنسخة الإنجليزية خطاً لاتينياً جميلاً اسمه Manrope، وكتب `font-family: "Manrope", sans-serif`، وهذا الخط لا يحتوي على أي حرف عربي. فماذا يفعل المتصفح عندما يجد حرفاً عربياً في عنصر خطه Manrope؟ يبحث في بقية السلسلة عن خط فيه هذا الحرف، فإذا لم يجد أخذ خطاً من خطوط النظام، وهذا يتم لكل حرف على حدة.

وقد استخدمنا في التجربة الأمر `CSS.getPlatformFontsForNode` من [بروتوكول DevTools](https://chromedevtools.github.io/devtools-protocol/tot/CSS/?ref=arabroot.io)، وهو الأمر الذي يقول لك أي خط استخدمه Chrome فعلاً لكل عنصر، وهو نفس ما تراه في لوحة Computed في DevTools تحت عنوان Rendered Fonts، والنتيجة كما يلي:

```text
   fonts used by #ls0: IBM Plex Sans Arabic (31 glyphs, web font)
   fonts used by #fb1: Manrope ExtraLight (10 glyphs, web font), Arial (28 glyphs)
   fonts used by #fb2: IBM Plex Sans Arabic (27 glyphs, web font), Manrope ExtraLight (10 glyphs, web font)
```

في السطر الثاني رسم Chrome الكلمة اللاتينية بخط Manrope، ثم رسم الحروف العربية الثمانية والعشرين بخط Arial من النظام، لأن السلسلة ليس فيها خط عربي، وArial على Windows فيه حروف عربية. وعلى جهاز Mac سوف يختار خطاً آخر، وعلى أندرويد خطاً ثالثاً، أي أن صفحتك العربية سوف تظهر بثلاثة أشكال مختلفة دون أن يقرر أحد ذلك. وفي السطر الثالث أضفنا الخط العربي بعد Manrope مباشرة: `font-family: "Manrope", "Plex Arabic", sans-serif`، فرسم Chrome الحروف العربية بخط IBM Plex Sans Arabic واللاتينية بخط Manrope، وهذه هي الطريقة الصحيحة لصفحة فيها اللغتان: خط لاتيني ثم خط عربي ثم بديل عام. ولاحظ أن اسم الخط ظهر Manrope ExtraLight مع أن الكلمة مرسومة بالوزن العادي، والسبب أن الملف خط متغير Variable Font، والاسم المسجل فيه هو اسم أول نسخة فيه، فلا تعتمد على هذا الاسم لتعرف الوزن.

![لقطة من Chrome لست عينات: جملة عربية بدون letter-spacing ثم بمسافة 2px ثم 0.2em والحروف متصلة في الثلاث مع اتساع المسافات بين الكلمات فقط، ثم جملة بخط Alexandria مقصوصة من الأعلى والأسفل في صندوق ارتفاعه 26 بكسلاً، ثم جملة بخط Manrope وحده يظهر فيها العربي بخط النظام، ثم بخط Manrope مع Plex Arabic يظهر فيها العربي بالخط الصحيح](https://arabroot.io/content/images/2026/10/css-layout-rtl-09-type.webp)

من الأعلى: letter-spacing لا يفصل الحروف في Chrome، وline-height: 1 يقص الحروف، والخط العربي الغائب من السلسلة يستبدله النظام

وعند اختيار الخط العربي نفسه انتبه إلى ثلاثة أشياء. الأول أن الخطوط العربية الجيدة تأتي بأوزان كثيرة، فاحمل فقط الأوزان التي تستخدمها، لأن كل وزن ملف مستقل قد يكون حجمه مئات الكيلوبايتات، وقد تحتاج إلى تقسيمه بالخاصية `unicode-range` حتى لا يحمل الزائر الإنجليزي حروفاً عربية لن يراها. والثاني أن الخطين العربي واللاتيني في نفس الصفحة يجب أن يتوازنا بصرياً، وهذا ما تقصده Apple عندما تقول إنك قد تحتاج إلى موازنة الخطين المتجاورين، فبعض الخطوط العربية تبدو أصغر من اللاتينية بنفس الحجم، وقد تحتاج إلى حجم أكبر قليلاً للعربية. والثالث أن الخط يحمل من سيرفرك أو من شبكة توصيل المحتوى CDN، فاقرأ عن التخزين المؤقت للملفات الثابتة في [مقال CDN](https://arabroot.io/articles/%D8%B4%D8%A8%D9%83%D8%A9-%D8%AA%D9%88%D8%B5%D9%8A%D9%84-%D8%A7%D9%84%D9%85%D8%AD%D8%AA%D9%88%D9%89-cdn-%D9%84%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6%D9%8A%D9%86/)، لأن ملف الخط الذي يحمل في كل زيارة يبطئ ظهور النص كما سوف نرى في قسم `font-display`. وتجد توصيات عامة عن تحميل الخطوط في [دليل web.dev لأفضل ممارسات الخطوط](https://web.dev/articles/font-best-practices?ref=arabroot.io).

### ارتفاع السطر Line Height للحروف العربية

الملاحظة الثانية كانت عن العنوان المقصوص في الجوال، وقد وجدنا السبب في مكون يعرض عنوان المنتج في سطر واحد، كتبه المطور هكذا: `height: 26px; line-height: 1; overflow: hidden`، والفكرة أن يقطع العنوان الطويل بدلاً من أن يفسد شكل البطاقة، وهي فكرة تعمل بالإنجليزية لأن الحروف اللاتينية لا تنزل كثيراً تحت خط القاعدة ولا تصعد كثيراً فوقه. أما العربية ففيها الهمزة تحت الألف في «إلى»، والياء والجيم والنون التي تنزل تحت الخط، والهمزة فوق الألف في «التأمين»، وكلها تحتاج إلى مساحة. والصورة التالية مبنية على القياس الحقيقي في Chrome لخط Alexandria بحجم 26 بكسلاً:

![عبارة إلى جانب التأمين مكبرة مع خطوط أفقية: حبر الحروف من 26 فوق خط القاعدة إلى 9 تحته، وصندوق الخط من 25 فوق إلى 7 تحت، وحد line-height: 1 من 22 فوق إلى 4 تحت، فتخرج همزة التأمين من الأعلى وهمزة إلى والنون والياء من الأسفل](https://arabroot.io/content/images/2026/10/css-layout-rtl-10-line-metrics.webp)

line-height: 1 أضيق من صندوق الخط العربي، فيقص overflow: hidden ما خرج منه

والأرقام كما طبعتها التجربة: صندوق الخط Font Box، أي المساحة التي يعلن الخط أنه يحتاجها، 25 بكسلاً فوق خط القاعدة و7 تحته، أي 32 بكسلاً، وهذا هو ارتفاع السطر عند `line-height: normal` بالضبط. أما الحبر الحقيقي للحروف، أي أعلى نقطة وأدنى نقطة مرسومة، فكان 26 فوق و9 تحت. وعند `line-height: 1` يصبح السطر 26 بكسلاً، فيوزع المتصفح النقص البالغ 6 بكسلات على الأعلى والأسفل، 3 لكل جهة، فيمتد السطر من 22 فوق الخط إلى 4 تحته، ويخرج الحبر 4 بكسلات من الأعلى و5 من الأسفل، ومع `overflow: hidden` يقص المتصفح هذه البكسلات. وقد قاس Chrome في العنصر نفسه `clientHeight = 26` و`scrollHeight = 29`، أي أن 3 بكسلات من صندوق الخط خرجت من أسفل الحد، ولم يحسب ما خرج من الأعلى لأن المحتوى لا يمرر إلى الأعلى أصلاً.

وللمقارنة كان صندوق خط Arial اللاتيني بنفس الحجم 24 فوق و6 تحت، والحبر الحقيقي لجملة إنجليزية 19 فوق و5 تحت، أي أن الحروف اللاتينية تبقى داخل صندوقها بفارق كبير، ولهذا لا يرى المطور المشكلة في النسخة الإنجليزية. والقاعدة العملية: اجعل `line-height` للنص العربي بين 1.6 و1.8 للفقرات، ولا تنزل في العناوين عن قيمة تتسع لصندوق الخط، وإذا أردت قطع النص في سطر واحد فاستخدم `text-overflow: ellipsis` مع `white-space: nowrap` دون ارتفاع ثابت، وإذا أردت عدة أسطر فاستخدم `line-clamp` مع `line-height` كاف. وفي التجربة كان السطر عند 1.7 يساوي 44.1875 بكسلاً وليس 44.2، لأن Chrome يحسب المواضع بوحدات صغيرة من البكسل ثم يقرب الناتج إليها، والقيم التي تقبلها الخاصية مشروحة في [صفحة MDN للخاصية line-height](https://developer.mozilla.org/en-US/docs/Web/CSS/line-height?ref=arabroot.io)، وأفضلها الرقم دون وحدة لأنه يورث كنسبة وليس كقيمة محسوبة.

### letter-spacing وtext-transform مع العربية

الملاحظة الثالثة أن عناوين الأقسام في التصميم الإنجليزي مكتوبة بأحرف كبيرة ومسافات بين الحروف، أي `text-transform: uppercase; letter-spacing: 0.2em`، وقد انتقلت نفس القاعدة إلى العربية. والتحذير المعروف بين مطوري الواجهات العربية أن `letter-spacing` يفصل الحروف العربية المتصلة فتصبح الكلمة حروفاً متفرقة، ولذلك توقعنا أن نرى ذلك في التجربة، ولكن Chrome 156 أعطى نتيجة مختلفة:

```text
   inline spans: Latin 136 -> 156px with 2px, Arabic 313 -> 321px with 2px
```

الكلمة الإنجليزية Typography فيها عشرة حروف، فزاد عرضها 20 بكسلاً، أي 2 بكسل بعد كل حرف كما هو متوقع. أما الجملة العربية فزادت 8 بكسلات فقط، وفي الصورة السابقة ترى أن الحروف بقيت متصلة في العينات الثلاث، وأن المسافات اتسعت بين الكلمات فقط. وهذا السلوك ليس صدفة، فمواصفة [CSS Text في قسم الخطوط المتصلة](https://www.w3.org/TR/css-text-3/?ref=arabroot.io#cursive-tracking) تقول إن المتصفح الذي لا يستطيع توسيع النص المتصل دون فصل حروفه يجب ألا يضيف مسافات بين حروفه أبداً، وأن يعامل كل كلمة كأنها حرف واحد، وهذا ما فعله Chrome. ولكن لا تعتمد على هذا، فالسلوك مختلف بين المتصفحات وإصداراتها، والأهم أن القاعدة نفسها لا معنى لها في العربية، فالمصمم أراد بها مظهراً لاتينياً، والحل أن تلغيها للعربية بالمحدد `:lang(ar)` أو `:dir(rtl)`: `letter-spacing: normal`.

ونفس الكلام عن `text-transform: uppercase`، فلا توجد أحرف كبيرة وصغيرة في العربية، فالقاعدة لا تفعل شيئاً وتبقى في الملف دون فائدة، ولكن انتبه إلى أنها تغير الكلمات اللاتينية داخل النص العربي، مثل اسم المنتج أو العلامة التجارية، فإذا كتب البائع اسم منتجه iPhone فسوف يظهر IPHONE. وإذا أردت تمييز العناوين في العربية فاستخدم الوزن Font Weight والحجم واللون بدلاً من المسافات والأحرف الكبيرة، وتنبه [صفحة MDN للخاصية letter-spacing](https://developer.mozilla.org/en-US/docs/Web/CSS/letter-spacing?ref=arabroot.io) في قسم التدويل إلى أن الحروف العربية يتوقع منها أن تبقى متصلة.

### font-display: متى يظهر النص؟

الملاحظة الأخيرة أن النص يظهر بخط النظام لحظة ثم يقفز إلى الخط العربي، وفي بعض الصفحات لا يظهر النص أصلاً لثوان على الاتصال البطيء. وهذا يتحكم فيه الوصف `font-display` داخل `@font-face`، وتعرف [مواصفة CSS Fonts](https://www.w3.org/TR/css-fonts-4/?ref=arabroot.io#font-display-desc) لكل خط ثلاث فترات: فترة الحجب Block Period يرسم فيها المتصفح النص غير مرئي إذا لم يصل الخط، وفترة التبديل Swap Period يرسم فيها النص بخط بديل ويبدله عند وصول الخط، ثم فترة الفشل التي يبقى فيها البديل. والقيمة `block` تعطي فترة حجب قصيرة توصي المواصفة بأن تكون 3 ثوان، والقيمة `swap` تعطي فترة حجب صغيرة جداً ثم تبديلاً دون حد.

ولنرى الفرق جعلنا سيرفر التجربة يتأخر 3 ثوان قبل أن يرسل ملف الخط، ثم صورنا الصفحة بعد ثانية واحدة من فتحها:

![لقطتان من Chrome بعد ثانية من فتح الصفحة: على اليسار مع font-display: swap يظهر عنوان عروض نهاية الأسبوع بخط Tahoma البديل، وعلى اليمين مع font-display: block المساحة فارغة والعنوان غير مرئي](https://arabroot.io/content/images/2026/10/css-layout-rtl-11-font-display.webp)

بعد ثانية واحدة والخط لم يصل: swap يعرض النص بالبديل، وblock يخفيه

```text
-- block: after 1s: font loading, heading width 253px | after 4s: font loaded, heading width 244px
-- swap: after 1s: font loading, heading width 253px | after 4s: font loaded, heading width 244px
```

في المخرج أعلاه لاحظ أن العنوان كان له عرض 253 بكسلاً بعد ثانية في الحالتين، حتى في `block` التي لا يرى فيها المستخدم شيئاً، والسبب أن المتصفح يحسب مكان النص بالخط البديل ثم يرسمه شفافاً، فالمساحة محجوزة والنص غير مرئي. وبعد 4 ثوان وصل الخط فأصبح العرض 244 بكسلاً، أي أن العنوان صغر 9 بكسلات عند التبديل، وهذا هو القفز الذي لاحظته المصممة، فكل ما تحت العنوان وبجانبه يتحرك. والاختيار بينهما قرار وليس قاعدة: `swap` للنصوص الطويلة والمحتوى الذي يريد القارئ أن يبدأ به فوراً، مع اختيار خط بديل قريب في المقاسات من الخط العربي حتى يقل القفز، و`block` للشعارات والعناوين القصيرة التي يفسد شكلها الخط البديل. وهناك قيمتان أخريان في [صفحة MDN للوصف font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display?ref=arabroot.io): `fallback` التي تتوقف عن التبديل بعد فترة قصيرة، و`optional` التي لا تبدل أبداً بعد أول رسم، وهي مناسبة إذا كان الخط مخزناً في الزيارات التالية.

## التجربة العملية Lab: نفس البطاقة في Chrome باتجاهين

كل الأرقام والصور في هذا المقال جاءت من تجربة واحدة تتكون من ملفين في مجلد `lab`: سيرفر صغير بمكتبة Node.js القياسية وحدها يقدم الصفحات والخطوط، وسكربت يفتح Chrome دون واجهة Headless ويتحكم فيه عبر بروتوكول DevTools، فيفتح كل صفحة ويقيسها ويصورها.

### الصفحات التي يقدمها السيرفر

السيرفر يبني كل صفحة من قالب واحد، ويغير فيها شيئين فقط حسب الرابط: السمة `dir` واللغة، وملف CSS الفيزيائي أو المنطقي. والجزء المهم منه هو دالة البطاقة:

```javascript
const card = (dir, css) => {
    const t = TEXT[dir];
    return `<!doctype html><html lang="${t.lang}" dir="${dir}"><head><meta charset="utf-8"><title>card</title>
<style>${BASE}${css === 'logical' ? LOGICAL : PHYSICAL}</style></head><body>
<div class="card" id="card">
  <button class="close" id="close" aria-label="close">✕</button>
  <div class="thumb" id="thumb"></div>
  <h3 id="title"><span id="titletext">${t.title}</span></h3>
  <p id="body">${t.body}</p>
  <div class="row">
    <button class="btn" id="buy">${ICON_DOWN}<span id="buylabel">${t.buy}</span></button>
    <a class="link" id="next" href="#">${t.next}${ICON_CHEV}</a>
    <span class="link" id="search">${ICON_FIND}${t.search}</span>
  </div>
  <div class="seg" id="seg"><button id="seg1">${t.seg[0]}</button><button id="seg2">${t.seg[1]}</button></div>
</div></body></html>`;
};
```

في الكود أعلاه لاحظ التالي:

- السمتان `lang` و`dir` على عنصر `html` نفسه، فالاتجاه موجود قبل أن يحمل أي CSS.
- الثابتان `PHYSICAL` و`LOGICAL` هما نفس ملفي CSS اللذين عرضناهما في أول المقال، و`BASE` فيه الألوان والأحجام المشتركة، فالفرق بين الصفحتين محصور في الخصائص التي ندرسها.
- لكل عنصر نريد قياسه `id` خاص، والنص داخل زر الشراء في `span` مستقل حتى نقيس المسافة بينه وبين الأيقونة بدقة.
- الصفحات الأخرى في نفس الملف: `/compare` تعرض البطاقة باللغتين جنباً إلى جنب في إطارين iframe، و`/layout` الشريط والشبكة، و`/type` عينات الخط، و`/fontload` الخط المتأخر، و`/nodir` الصفحة التي اتجاهها من CSS فقط. والمسار `/slowfont/` ينتظر 3 ثوان قبل إرسال الخط.

### كيف نقيس البطاقة؟

سكربت القياس يستخدم مساعد Chrome الموجود في مستودع الموقع، ويفتح البطاقة أربع مرات: فيزيائية ومنطقية، كل منهما بالإنجليزية والعربية، ويقيس فيها ما يلي:

```javascript
for (const [css, dir] of [['physical', 'ltr'], ['physical', 'rtl'], ['logical', 'ltr'], ['logical', 'rtl']]) {
    await b.goto(`${URL0}/card?dir=${dir}&css=${css}`, { wait: 600 });
    const m = await b.evaluate(`(() => {
        const R = (s) => document.querySelector(s).getBoundingClientRect(), cs = (s) => getComputedStyle(document.querySelector(s));
        const card = R('#card'), icon = R('#buy .icon'), label = R('#buylabel'), close = R('#close'), title = R('#titletext'), thumb = R('#thumb');
        const iconSide = icon.left < label.left ? 'left' : 'right';
        const gapToText = Math.round(iconSide === 'left' ? label.left - icon.right : icon.left - label.right);
        const overlap = Math.max(0, Math.min(close.right, title.right) - Math.max(close.left, title.left));
        return [
          'icon ' + iconSide + ' of text, gap to text ' + gapToText + 'px, margin L/R ' + cs('#buy .icon').marginLeft + '/' + cs('#buy .icon').marginRight,
          'close button at ' + (close.left - card.left < card.right - close.right ? 'left' : 'right') + ', overlaps title by ' + Math.round(overlap) + 'px',
          'accent border L/R ' + cs('#card').borderLeftWidth + '/' + cs('#card').borderRightWidth + ', padding L/R ' + cs('#card').paddingLeft + '/' + cs('#card').paddingRight,
          'thumb on the ' + (thumb.left - card.left < card.right - thumb.right ? 'left' : 'right') + ', text-align ' + cs('#body').textAlign,
          'segment first button radius TL/TR ' + cs('#seg1').borderTopLeftRadius + '/' + cs('#seg1').borderTopRightRadius + ', chevron transform ' + cs('#next .chev').transform,
        ];
    })()`);
    console.log(`-- ${css} ${dir}`); m.forEach(x => console.log('   ' + x));
}
```

في الكود أعلاه لاحظ التالي:

- الدالة `getBoundingClientRect` تعطي مكان العنصر على الشاشة بعد التخطيط، أي بالبكسل الحقيقي، فنعرف منها في أي جهة وقع العنصر.
- الدالة `getComputedStyle` تعطي القيم النهائية بعد أن حول المتصفح الخصائص المنطقية إلى فيزيائية، ولذلك نقرأ `marginLeft` و`marginRight` حتى في الصفحة المنطقية، فنرى أين وضع المتصفح الهامش فعلاً.
- المسافة بين الأيقونة والنص تحسب حسب الجهة التي وقعت فيها الأيقونة، لأن الأيقونة في العربية على يمين النص.

ثم يكمل السكربت بنفس الطريقة: يقيس مواضع الشريط والشبكة في نافذة عرضها 1000 بكسل ثم 390، ويقيس الخطوط بالدالة [measureText](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText?ref=arabroot.io) في Canvas التي تعطي صندوق الخط والحبر الحقيقي، ويسأل Chrome عن الخطوط المستخدمة بالأمر `CSS.getPlatformFontsForNode`، ثم يحفظ الصور في مجلد `images`.

### تشغيل التجربة

تحتاج فقط إلى Node.js وChrome، ولا توجد أي حزمة من npm غير `sharp` التي يستخدمها المساعد لحفظ الصور بصيغة WebP. نشغل السيرفر في نافذة:

```bash
node content/articles/css-layout-rtl/lab/server.mjs
```

ثم نشغل القياس في نافذة أخرى:

```bash
node content/articles/css-layout-rtl/lab/measure.mjs
```

والمخرج سوف يكون كما يلي:

```text
Chrome: Chrome/156.0.8078.49

== 1) the same card, physical vs logical properties
-- physical ltr
   icon left of text, gap to text 8px, margin L/R 0px/8px
   close button at right, overlaps title by 0px
   accent border L/R 4px/0px, padding L/R 16px/48px
   thumb on the left, text-align left
   segment first button radius TL/TR 10px/0px, chevron transform none
-- physical rtl
   icon right of text, gap to text 0px, margin L/R 0px/8px
   close button at right, overlaps title by 0px
   accent border L/R 4px/0px, padding L/R 16px/48px
   thumb on the left, text-align left
   segment first button radius TL/TR 10px/0px, chevron transform none
-- logical ltr
   icon left of text, gap to text 8px, margin L/R 0px/8px
   close button at right, overlaps title by 0px
   accent border L/R 4px/0px, padding L/R 16px/48px
   thumb on the left, text-align start
   segment first button radius TL/TR 10px/0px, chevron transform none
-- logical rtl
   icon right of text, gap to text 8px, margin L/R 8px/0px
   close button at left, overlaps title by 0px
   accent border L/R 0px/4px, padding L/R 48px/16px
   thumb on the right, text-align start
   segment first button radius TL/TR 0px/10px, chevron transform matrix(-1, 0, 0, 1, 0, 0)

== 2) Flexbox and Grid follow dir (window 1000px)
-- dir=ltr flex-direction:row  logo x=36 cart x=890 | aside x=20 item1 x=236 item2 x=488 | list columns 240px 240px 240px
-- dir=rtl flex-direction:row  logo x=885 cart x=36 | aside x=780 item1 x=524 item2 x=272 | list columns 240px 240px 240px
-- dir=rtl flex-direction:row-reverse  logo x=36 cart x=268 | aside x=780 item1 x=524 item2 x=272 | list columns 240px 240px 240px
-- dir=ltr flex-direction:row-reverse  logo x=874 cart x=619 | aside x=20 item1 x=236 item2 x=488 | list columns 240px 240px 240px
-- dir=rtl at 390px: list columns 350px, main above aside: true

== 3) Arabic type
   text width: normal 612px, 2px 612px, 0.2em 612px
   inline spans: Latin 136 -> 156px with 2px, Arabic 313 -> 321px with 2px
   clipped box: clientHeight 26, scrollHeight 29
   line box height at 26px: Alexandria line-height 1 = 26, normal = 32, 1.7 = 44.1875 | Arial normal = 31
   Alexandria 26px Arabic: font box 25.0 up / 7.0 down, ink 26.0 up / 9.0 down
   Alexandria 26px «إلى جانب التأمين»: font box 25.0 up / 7.0 down, ink 26.0 up / 9.0 down, width 188.3
   Arial 26px Latin:       font box 24.0 up / 6.0 down, ink 19.0 up / 5.0 down
   fonts used by #ls0: IBM Plex Sans Arabic (31 glyphs, web font)
   fonts used by #fb1: Manrope ExtraLight (10 glyphs, web font), Arial (28 glyphs)
   fonts used by #fb2: IBM Plex Sans Arabic (27 glyphs, web font), Manrope ExtraLight (10 glyphs, web font)

== 4) font-display: the web font answers after 3 seconds
-- block: after 1s: font loading, heading width 253px | after 4s: font loaded, heading width 244px
-- swap: after 1s: font loading, heading width 253px | after 4s: font loaded, heading width 244px

== 5) direction from CSS only (no dir attribute)
-- /nodir: html dir="", body direction rtl, chevron matches :dir(rtl) false, transform none
-- /card?dir=rtl&css=logical: html dir="rtl", body direction rtl, chevron matches :dir(rtl) true, transform matrix(-1, 0, 0, 1, 0, 0)
```

### المخرج سطراً بسطر

القسم الأول هو البطاقة، وفيه أربع مجموعات، ونقرؤها بالمقارنة بين كل مجموعة والتي قبلها:

- **`physical ltr`:** هذا هو المرجع، أي ما أراده المصمم: الأيقونة على يسار النص وبينهما 8 بكسلات، وزر الإغلاق على اليمين، والحد البرتقالي 4 بكسلات على اليسار، والحشو الواسع 48 على اليمين حيث زر الإغلاق، والصورة المصغرة على اليسار، والزاوية المستديرة للزر الأول على يساره.
- **`physical rtl`:** قارن كل سطر بالمرجع. الأيقونة انتقلت إلى يمين النص لأنها أول عنصر في السطر، ولكن الهامش بقي `0px/8px`، أي على يمينها، فأصبحت المسافة بينها وبين النص صفراً. وكل القيم الأخرى بقيت كما هي حرفياً: الحد على اليسار، والصورة على اليسار، والمحاذاة `left`، والزاوية على اليسار، والسهم دون قلب. أي أن الخصائص الفيزيائية فعلت بالضبط ما كتب فيها، والخطأ في أننا كتبنا جهة الشاشة ونحن نقصد جهة القراءة.
- **`logical ltr`:** نفس أرقام المرجع في كل سطر، ما عدا المحاذاة التي ظهرت `start` بدلاً من `left`، وهذا يثبت أن الترحيل إلى الخصائص المنطقية لا يغير الصفحة الإنجليزية.
- **`logical rtl`:** كل شيء انعكس بالمقدار الصحيح: الهامش `8px/0px` أي على يسار الأيقونة، فعادت المسافة 8 بكسلات، وزر الإغلاق على اليسار، والحد `0px/4px` على اليمين، والحشو `48px/16px` انعكس معه، والصورة على اليمين، والزاوية `0px/10px` على يمين الزر الأول، والسهم مقلوب بالمصفوفة `matrix(-1, 0, 0, 1, 0, 0)`، أي ضرب الإحداثي الأفقي في سالب واحد.
- **السطر `overlaps title by 0px`:** بقي صفراً في الحالات الأربع، فزر الإغلاق لم يغط العنوان في أي منها، لأن الحشو الذي يحجز له المكان ينتقل معه دائماً، سواء بقيا معاً على اليمين في الفيزيائية أو انتقلا معاً إلى اليسار في المنطقية.

والقسم الثاني هو التخطيط، وكل الأرقام فيه هي الإحداثي الأفقي x لبداية العنصر من يسار النافذة:

- في `dir=ltr` الشعار عند 36 والسلة عند 890، والعمود الجانبي عند 20 وأول منتج عند 236 وثانيه عند 488، أي أن الترتيب من اليسار.
- في `dir=rtl` الشعار عند 885 والسلة عند 36، والعمود الجانبي عند 780، وأول منتج عند 524 وثانيه عند 272، أي أن الترتيب كله أصبح من اليمين دون أي تعديل في CSS.
- في `rtl` مع `row-reverse` عاد الشعار إلى 36، وهذا القلب المزدوج الذي شرحناه، ولاحظ أن الشبكة والعمود الجانبي بقيا عند نفس أرقامهما لأن `row-reverse` طبق على الشريط فقط.
- السطر الرابع، أي `ltr` مع `row-reverse`، يبين الحالة المعاكسة: الشعار ذهب إلى اليمين عند 874، والسلة عند 619 بجانب الروابط لنفس سبب الهامش التلقائي.
- في كل الحالات بعرض 1000 كانت الشبكة ثلاثة أعمدة بعرض 240، وفي 390 أصبحت عموداً واحداً بعرض 350، والمنطقة main فوق side.

والقسم الثالث هو الخطوط، وقد شرحنا أغلب أسطره في أقسامها، ونضيف هنا ملاحظتين. السطر الأول يقول إن عرض العينات الثلاث 612 بكسلاً، وهذا ليس عرض النص وإنما عرض العنصر الكتلي الذي يحتويه، لأنه يأخذ عرض الأب مهما كان النص، ولهذا أضفنا السطر الثاني الذي يقيس عناصر `span` سطرية عرضها هو عرض النص فعلاً. وسطر ارتفاع الأسطر يقارن خط Alexandria بخط Arial عند `line-height: normal`: الأول 32 بكسلاً والثاني 31، أي أن الفرق في الارتفاع الكلي صغير، والمشكلة ليست في ارتفاع الخط العربي وإنما في أن حبره يقترب من حدود صندوقه ويخرج منها.

والقسم الرابع والخامس شرحناهما في قسمي `font-display` وسمة `dir`.

### مشكلتان واجهتانا أثناء التجربة

المشكلة الأولى كانت في صفحة المقارنة نفسها، فقد كتبنا تحت كل إطار عنواناً بسيطاً هو `dir="rtl"`، وصفحة المقارنة عربية، فظهر العنوان في الصورة الأولى هكذا: `"dir="rtl`، أي أن علامة التنصيص الأخيرة قفزت إلى بداية السطر. والسبب أن علامات التنصيص محايدة الاتجاه، وفي نهاية سطر عربي تأخذ اتجاه الفقرة فتذهب إلى يسار النص الإنجليزي. والحل كان وضع النص الإنجليزي في عنصر `bdi` مع `dir="ltr"` كما في [صفحة MDN لعنصر bdi](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/bdi?ref=arabroot.io)، وهذا تذكير بأن الخصائص المنطقية تحل مشكلة مكان الصناديق، ولا تحل مشكلة ترتيب الحروف داخل السطر، وهذه موضوع مقال BiDi.

والمشكلة الثانية كانت في قياس `font-display`، ففي التشغيل الأول قسنا عرض العنوان `h2` فأعطى 520 بكسلاً في الحالتين قبل وصول الخط وبعده، فظننا أن التبديل لا يغير شيئاً، ثم انتبهنا إلى أن `h2` عنصر كتلي عرضه عرض الصفحة دائماً، فوضعنا النص في `span` وقسناه، فظهر الفرق الحقيقي من 253 إلى 244\. ونفس الخطأ يقع فيه من يكتب اختباراً آلياً يقارن عرض عنصر كتلي قبل تغيير الخط وبعده، والقاعدة: قس العنصر السطري أو استخدم `Range.getBoundingClientRect` على النص نفسه.

### ماذا يحدث إذا وضعت الاتجاه في CSS بدلاً من dir؟

هذه هي التجربة الخامسة في المخرج، وقد صنعناها لأن بعض أطر الواجهات وقوالب المواقع تغير الاتجاه بإضافة صنف إلى `body` يحتوي على `direction: rtl` بدلاً من تغيير السمة. والنتيجة أن الاتجاه المحسوب `body direction rtl` صحيح في الحالتين، فالنص والتخطيط على اليمين، ولكن `:dir(rtl)` أعاد `false` للسهم، فلم ينقلب. فإذا كانت الأيقونات في مشروعك تقلب بـ `:dir()` فلا بد أن يغير الإطار السمة `dir` على `html`، وإذا لم يكن ذلك ممكناً فاستخدم المحدد `[dir="rtl"] .chev` على صنف تضعه أنت، مع العلم أن هذا المحدد لا يرث الاتجاه من عنصر `dir="auto"` كما يفعل `:dir()`.

## كيف تمنع رجوع الأخطاء Regression بعد الإصلاح؟

بعد أن أصلح الفريق البطاقة بقي سؤال: كيف نضمن أن المكون التالي الذي يكتبه مطور جديد لن يعيد `margin-left`؟ هناك ثلاث طبقات من الحماية، ولكل منها تكلفة.

- **مراجعة CSS آلياً:** أداة فحص الكود Linter مثل Stylelint يمكن أن تمنع الخصائص الفيزيائية الأفقية بالقاعدة [property-disallowed-list](https://stylelint.io/user-guide/rules/property-disallowed-list/?ref=arabroot.io) التي تمنع أسماء خصائص تحددها أنت، فيفشل خط CI عند أي `margin-left` جديد، مع استثناءات مكتوبة للحالات المقصودة مثل الظلال.
- **اختبار المواضع في المتصفح:** نفس فكرة سكربت القياس، ولكن داخل إطار اختبار، فتتحقق أن الصورة المصغرة على يمين النص في الصفحة العربية، كما فعلنا في [اختبار الصفحة العربية في مقال Playwright](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-e2e-%D9%85%D8%B9-playwright-%D8%A8%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84/#rtl) عندما تحققنا أن السلة على يسار قائمة المنتجات بالدالة `boundingBox`.
- **مقارنة الصور Visual Regression:** تصوير المكونات باللغتين ومقارنتها بصور معتمدة، وهي أقوى طبقة لأنها تلتقط كل ما في الصور السابقة، وأكثرها تكلفة لأن كل تغيير مقصود في التصميم يحتاج إلى تحديث الصور.

والترتيب العملي المناسب لأغلب الفرق: ابدأ بقاعدة الفحص لأنها الأرخص وتمنع المشكلة من مصدرها، ثم أضف اختبارات المواضع للمكونات الأساسية مثل الشريط والبطاقة والنافذة، ثم مقارنة الصور عندما يصبح عندك نظام تصميم Design System مستقر.

## أخطاء شائعة

1. **ملف CSS منفصل للعربية:** يضاعف الصيانة ويختلف عن الأصل مع الوقت، فاكتب ملفاً واحداً بالخصائص المنطقية.
2. **الاتجاه في CSS وحده:** `direction: rtl` دون `dir` يفقدك `:dir()` ويضيع الاتجاه إذا لم يحمل CSS، فضع `dir` و`lang` على `html`.
3. **`row-reverse` أو `order` للعربية:** يقلب الصف مرتين ويفصل ترتيب العين عن ترتيب لوحة المفاتيح، فاترك Flexbox يتبع الاتجاه.
4. **الاختصار ذو الأربع قيم:** `padding: 16px 48px 16px 16px` فيزيائي دائماً، فقسمه إلى `padding-block` و`padding-inline`.
5. **قلب كل الأيقونات:** العدسة والساعة وعلامة الصح وسهم التنزيل والشعارات لا تقلب، فقط ما له معنى مرتبط باتجاه القراءة.
6. **نسيان `transform` وموضع الخلفية:** لا ينقلبان مع الاتجاه، فراجع القوائم المنزلقة والأيقونات داخل الحقول.
7. **خط لاتيني وحده في السلسلة:** يجعل النظام يختار الخط العربي، فيختلف شكل الصفحة بين الأجهزة، فضع خطاً عربياً صريحاً بعد اللاتيني.
8. **`line-height` صغير مع `overflow: hidden`:** يقص همزات الحروف ونقاطها وما ينزل منها تحت الخط، فاترك للعربية ارتفاعاً لا يقل عن صندوق الخط.
9. **نقل `letter-spacing` و`uppercase` من التصميم الإنجليزي:** لا معنى لهما في العربية، وقد تفصل المسافات الحروف في متصفح آخر، فألغهما بـ `:lang(ar)`.
10. **الاختبار بعين لا تقرأ العربية:** كثير من الأخطاء تبدو سليمة لمن لا يقرأ، مثل زر الإغلاق في بداية القراءة، فاجعل من يراجع الواجهة العربية قارئاً لها.

## قائمة مراجعة Checklist قبل إطلاق الواجهة العربية

| البند                                                           | كيف تتحقق                                                       | الإصلاح                                            |
| --------------------------------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------- |
| dir="rtl" وlang="ar" على html                                   | افتح المصدر أو اختبر السمتين آلياً                              | ضعهما في القالب الرئيسي وليس في CSS                |
| لا توجد خاصية فيها left أو right للهوامش والحشو والحدود والموضع | ابحث في الملفات عن \-left و\-right وleft: وright:               | استبدلها بـ inline-start وinline-end وinset-inline |
| المحاذاة والطفو                                                 | ابحث عن text-align: left وfloat: left                           | start وinline-start، أو احذف القيمة الافتراضية     |
| الزوايا المستديرة للعناصر المتلاصقة                             | انظر إلى أطراف الأزرار المجمعة والتبويبات                       | border-start-start-radius وأخواتها                 |
| Flexbox وGrid دون قلب يدوي                                      | ابحث عن row-reverse وorder في ملفات العربية                     | احذفها واترك الاتجاه يقلب الترتيب                  |
| الأيقونات                                                       | راجع كل أيقونة: هل معناها مرتبط باتجاه القراءة؟                 | اقلب ما يلزم بـ :dir(rtl) فقط                      |
| الإزاحة والحركة                                                 | ابحث عن translateX وbackground-position وقيم left في JavaScript | اضربها في متغير الاتجاه أو اقرأ document.dir       |
| الخط العربي في السلسلة                                          | لوحة Rendered Fonts في DevTools على نص عربي                     | خط عربي صريح بعد اللاتيني، ثم بديل عام             |
| ارتفاع السطر                                                    | ابحث عن line-height: 1 مع ارتفاع ثابت وoverflow: hidden         | 1.6 إلى 1.8 للفقرات، وقطع النص بـ text-overflow    |
| تحميل الخط                                                      | افتح الصفحة مع تبطئة الشبكة في DevTools                         | font-display مناسب لكل خط، وتخزين مؤقت طويل للملف  |
| العرض الضيق                                                     | اعرض الصفحة بعرض 390 بكسلاً باللغتين                            | minmax وauto-fit والمناطق المسماة                  |

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- المتصفح يرتب الصفحة بمحورين: محور السطر الذي يبدأ من اليمين في العربية، ومحور الكتل من الأعلى إلى الأسفل، وكل ما كتب بأسماء الشاشة لا يعرف شيئاً عن هذا.
- الخصائص الفيزيائية لا تعطي خطأ في الصفحة العربية، وإنما تنفذ ما كتب فيها حرفياً، فتظهر الأخطاء في المسافات والحدود والصور والمحاذاة والزوايا.
- الخصائص المنطقية مثل `margin-inline-start` و`inset-inline-end` و`border-start-start-radius` و`text-align: start` تعطي نفس النتيجة في الإنجليزية بالضبط، وتنعكس وحدها في العربية، فملف واحد يكفي للغتين.
- ضع `dir` و`lang` على `html`، فالاتجاه في CSS وحده يفقدك المحدد `:dir()`.
- Flexbox وGrid يتبعان الاتجاه دون أي تعديل، والقلب اليدوي بـ `row-reverse` يعيد الترتيب الإنجليزي.
- اقلب الأيقونات التي يرتبط معناها باتجاه القراءة فقط، واترك الأشياء الحقيقية والاتجاهات الحقيقية والشعارات.
- الخط العربي يحتاج إلى مكان صريح في سلسلة الخطوط، وإلى ارتفاع سطر أكبر من اللاتيني، ولا يحتاج إلى `letter-spacing` أو `uppercase`، واختر `font-display` حسب نوع النص.

وفي مرحلة [هندسة الواجهات وتطبيقات الويب](https://arabroot.io/start/#stage-web-frontend) تجد المقالات التي تكمل هذا المقال، ومنها مقال المتصفح من الداخل الذي يشرح ما يحدث عند حساب التخطيط والرسم، ومقال التعريب والتوطين الذي يشرح ترتيب النص داخل السطر والتواريخ والأرقام:

[![](https://arabroot.io/content/images/2026/10/browser-internals-dom-cover-cover.webp)دليلكيف يرسم المتصفح صفحتك؟ من HTML إلى البكسلاتماذا يفعل المتصفح بين وصول HTML وظهور الصفحة، ولماذا تتجمد صفحة فيها ألفا صف، وكيف تقيس كل مرحلة في Chrome وتصلحها.](https://arabroot.io/articles/%D9%83%D9%8A%D9%81-%D9%8A%D8%B9%D9%85%D9%84-%D8%A7%D9%84%D9%85%D8%AA%D8%B5%D9%81%D8%AD-%D9%85%D9%86-%D8%A7%D9%84%D8%AF%D8%A7%D8%AE%D9%84-dom-%D9%88%D8%A7%D9%84%D8%AA%D8%AE%D8%B7%D9%8A%D8%B7-%D9%88%D8%A7%D9%84%D8%B1%D8%B3%D9%85/)[![](https://arabroot.io/content/images/2026/10/i18n-l10n-arabic-cover-cover.webp)دليلالتعريب i18n للمطورين: لماذا ينقلب النص العربي في الواجهة؟لماذا ينقلب النص المختلط في الواجهة العربية وكيف تعزله، وكيف تنسق الأرقام والتواريخ وصيغ الجمع العربية بأدوات المتصفح نفسها.](https://arabroot.io/articles/%D8%A7%D9%84%D8%AA%D8%B9%D8%B1%D9%8A%D8%A8-i18n-%D9%88%D8%A7%D9%84%D9%86%D8%B5-%D8%AB%D9%86%D8%A7%D8%A6%D9%8A-%D8%A7%D9%84%D8%A7%D8%AA%D8%AC%D8%A7%D9%87-bidi-%D9%84%D9%84%D9%85%D8%B7%D9%88%D8%B1%D9%8A%D9%86/)[![](https://arabroot.io/content/images/2026/10/js-event-loop-cover-cover.webp)دليلحلقة الأحداث Event Loop: لماذا تتأخر النقرة في صفحتك؟كيف ترتب JavaScript المهام والوعود والرسم على خيط واحد، ولماذا تنتظر النقرة خلف مهمة طويلة، وكيف تقسم العمل لتبقى الصفحة سريعة.](https://arabroot.io/articles/%D8%AD%D9%84%D9%82%D8%A9-%D8%A7%D9%84%D8%A7%D8%AD%D8%AF%D8%A7%D8%AB-event-loop-%D9%81%D9%8A-javascript/)[![](https://arabroot.io/content/images/2026/10/accessibility-basics-cover-cover.webp)دليلسهولة الوصول Accessibility: موقع وتطبيق يستخدمه الجميعكيف تبني صفحات وتطبيقات يستخدمها الجميع بلوحة المفاتيح وقارئ الشاشة، من عناصر HTML الأصلية إلى النوافذ والنماذج العربية.](https://arabroot.io/articles/%D8%B3%D9%87%D9%88%D9%84%D8%A9-%D8%A7%D9%84%D9%88%D8%B5%D9%88%D9%84-accessibility-%D9%83%D9%8A%D9%81-%D8%AA%D8%A8%D9%86%D9%8A-%D9%85%D9%88%D9%82%D8%B9%D8%A7-%D9%8A%D8%B3%D8%AA%D8%AE%D8%AF%D9%85%D9%87-%D8%A7%D9%84%D8%AC%D9%85%D9%8A%D8%B9/)[![](https://arabroot.io/content/images/2026/10/e2e-playwright-cover-cover.webp)دليلاختبارات E2E مع Playwright: من أول اختبار إلى CIكيف تكتب اختبارات متصفح ثابتة لموقع عربي مع Playwright، وتشخص الاختبار المتقلب، وتقسمها على أكثر من جهاز في خط CI.](https://arabroot.io/articles/%D8%A7%D8%AE%D8%AA%D8%A8%D8%A7%D8%B1%D8%A7%D8%AA-e2e-%D9%85%D8%B9-playwright-%D8%A8%D8%A7%D9%84%D8%AA%D9%81%D8%B5%D9%8A%D9%84/)[![](https://arabroot.io/content/images/2026/10/interview-frontend-cover-cover.webp)دليلأسئلة مقابلة مطور Frontend من المبتدئ إلى الخبيربنك أسئلة لمقابلة مطور Frontend، لكل سؤال إجابة المبتدئ وإجابة الخبير وأسئلة متابعة، مع تمارين عملية شغلناها فعلاً.](https://arabroot.io/articles/%D8%A7%D8%B3%D8%A6%D9%84%D8%A9-%D9%85%D9%82%D8%A7%D8%A8%D9%84%D8%A9-%D9%85%D8%B7%D9%88%D8%B1-frontend-%D9%85%D9%86-%D8%A7%D9%84%D9%85%D8%A8%D8%AA%D8%AF%D8%A6-%D8%A7%D9%84%D9%89-%D8%A7%D9%84%D8%AE%D8%A8%D9%8A%D8%B1/)

## سجل التحديثات

- أكتوبر 2026: كتابة المقال، وتنفيذ التجربة في Chrome 156 دون واجهة عبر بروتوكول DevTools مع Node.js 22.14، وقياس البطاقة والتخطيط والخطوط باتجاهين.