التبويبات
Tabsrole="tablist"
اسمه كمانتابس، التابات، الألسنة، علامات التبويب، شريط التبويبات، tabbed interface، tab group، tablist، tabpanel، tabbed panels، tab control
التبويبات صفّ عناوين متجاورة، يفتح كل عنوان لوحاً واحداً من المحتوى في نفس المساحة، ولوح واحد فقط ظاهر في كل لحظة. وشرطها الجوهري أن الألواح أشقّاء متكافئة في المستوى لا ترتيب بينها، وأن التنقّل بينها لا يكلّف المستخدم شيئاً ولا يُفقده ما كتبه. وهي ليست مؤشّر خطوات، فالخطوات رحلة مرتّبة لها بداية ونهاية وحالة إنجاز، أمّا التبويبات فلا تُنجَز ولا يُشترط المرور عليها كلّها. وليست مفتاحاً مقسّماً (segmented control)، لأن المفتاح المقسّم يختار قيمة لحقل واحد ويُبنى في الغالب على radiogroup لا على tablist، بينما التبويب يستبدل لوح محتوى كاملاً. وأخطر الفوارق كلّها فارق الروابط: أي «تبويب» يغيّر الـ URL ويحمّل صفحة جديدة هو رابط تنقّل لا تبويب، ويبقى <a href> داخل <nav>، ووضع role="tab" عليه يكسر الفتح في نافذة جديدة وزرّ الرجوع معاً. والقاعدة العملية أن الانتقال الذي يغيّر العنوان في شريط المتصفّح تنقّل، والذي يغيّر المعروض داخل الصفحة وحده تبويب.
لو قلت عليه…
العيّنة الحيّة
تفاعل مع الديمو. كل الأجزاء حقيقية ومرقّمة.
تشريح العنصر: كل جزء واسمه
مرّر على أي سطر ليتحدّد مكانه في الديمو فوق.
برومبت البناء
سلوكه في الاتجاه من اليمين لليسار
الجزء ده حصري عندنا.
مفاتيح الأسهم تتبادل
ينعكسفي الشريط الأفقي ينصّ نمط ARIA على أن ArrowRight ينتقل إلى التبويب التالي في LTR، وفي RTL ينقلب المعنى فيصير ArrowLeft هو «التالي» و ArrowRight هو «السابق». اقرأ الاتجاه وقت التنفيذ من getComputedStyle(list).direction أو من list.matches(":dir(rtl)") ثم اضرب مقدار الحركة في -1، ولا تستنتجه من لغة الواجهة لأن أي حاوية قد تُقلب وحدها. Home و End لا ينقلبان: Home هو أوّل تبويب في الـ DOM دائماً، وهو الأقصى يميناً في العربية.
موضع الخطّ النشط
ينعكسoffsetLeft يُقاس من الحافّة اليسرى مهما كان الاتجاه، فحساب موضع الخطّ منه يوقفه تحت التبويب الخطأ في RTL. قِس المسافة من البداية المنطقية بدلاً منه: في LTR tab.getBoundingClientRect().left − list.left، وفي RTL list.right − tab.right. ثم حرّك الخطّ بـ inset-inline-start، أو بـ translateX مضروباً في متغيّر --dir قيمته -1 داخل [dir="rtl"]، لأن translateX نفسه فيزيائي ولا ينعكس مع الصفحة.
تمرير الشريط عند الفيض
ينعكسالشريط يستقرّ عند بدايته المنطقية، أي عند الحافّة اليمنى في العربية. في المتصفّحات الحديثة تكون scrollLeft صفراً عند تلك الحافّة وتصبح سالبة كلّما مرّرت يساراً، فأي زرّ «التالي» مكتوب بـ el.scrollLeft += 200 سيمرّر في الاتجاه المعاكس، وأي مقارنة على قيمة التمرير يجب أن تمرّ بـ Math.abs. استخدم el.scrollBy({ left: delta * dir }) أو الأفضل scrollIntoView({ inline: "nearest" }) لأنها تعمل على المحور المنطقي، واستبدل scroll-padding-left بـ scroll-padding-inline-start.
تدرّجات حوافّ التمرير
ينعكسكلمتا to right و to left فيزيائيتان دائماً، إذ لا يقبل linear-gradient كلمات اتجاه منطقية. ولذلك لن ينعكس التدرّج الذي يقول «هناك المزيد» وحده مهما ضبطت اتجاه الصفحة. ثبّت الطبقتين بـ inset-inline-start و inset-inline-end فينتقلان للجهة الصحيحة تلقائياً، ثم اقلب زاوية التدرّج نفسها داخل [dir="rtl"] أو مرّرها عبر متغيّر --fade-angle. نفس القيد ينطبق على mask-image لو استعملت القصّ بدل التدرّج.
ما لا يُعكس أبداً
لا ينعكسلا تعكس ترتيب التبويبات بـ flex-direction: row-reverse. الاتجاه rtl وحده كافٍ ليضع أوّل تبويب في اليمين، أمّا row-reverse فيقلب الشكل ويترك ترتيب الـ DOM كما هو. والمواصفة تنصّ صراحةً على أن إعادة الترتيب البصري في flexbox لا تمسّ ترتيب التنقّل، فينتهي بك الأمر وسهم لوحة المفاتيح يتحرّك عكس ما تراه العين. كذلك aria-orientation يصف محور التخطيط لا اللغة فيبقى "horizontal" في العربية، وأيقونات التبويبات المحايدة (ترس الإعدادات، السلّة، الجرس) لا تُقلب.
أسماؤه في الكود
كل سطر هو كلمة مكتبة واحدة عن نفس الشيء. خد السطر اللي بيكلّم مشروعك.
شوف كمان
آخر تحديث · 2026-08-19