UIDex

التبويبات

Tabs

role="tablist"

اسمه كمانتابس، التابات، الألسنة، علامات التبويب، شريط التبويبات، tabbed interface، tab group، tablist، tabpanel، tabbed panels، tab control

التبويبات صفّ عناوين متجاورة، يفتح كل عنوان لوحاً واحداً من المحتوى في نفس المساحة، ولوح واحد فقط ظاهر في كل لحظة. وشرطها الجوهري أن الألواح أشقّاء متكافئة في المستوى لا ترتيب بينها، وأن التنقّل بينها لا يكلّف المستخدم شيئاً ولا يُفقده ما كتبه. وهي ليست مؤشّر خطوات، فالخطوات رحلة مرتّبة لها بداية ونهاية وحالة إنجاز، أمّا التبويبات فلا تُنجَز ولا يُشترط المرور عليها كلّها. وليست مفتاحاً مقسّماً (segmented control)، لأن المفتاح المقسّم يختار قيمة لحقل واحد ويُبنى في الغالب على radiogroup لا على tablist، بينما التبويب يستبدل لوح محتوى كاملاً. وأخطر الفوارق كلّها فارق الروابط: أي «تبويب» يغيّر الـ URL ويحمّل صفحة جديدة هو رابط تنقّل لا تبويب، ويبقى <a href> داخل <nav>، ووضع role="tab" عليه يكسر الفتح في نافذة جديدة وزرّ الرجوع معاً. والقاعدة العملية أن الانتقال الذي يغيّر العنوان في شريط المتصفّح تنقّل، والذي يغيّر المعروض داخل الصفحة وحده تبويب.

لو قلت عليه…

«الأزرار اللي فوق وكل ما أدوس على واحد المحتوى تحت يتغيّر»«الخط الصغير اللي بيزحلق تحت العنوان المختار»«التابات اللي زي بتاعة المتصفّح بس جوّه الصفحة»«أقسام جنب بعض وواحد بس اللي بيبان»«شريط العناوين اللي بيبدّل من غير ما الصفحة تتحمّل»

العيّنة الحيّة

تفاعل مع الديمو. كل الأجزاء حقيقية ومرقّمة.

ثلاثة طلبات قيد التنفيذ، واثنا عشر طلباً مكتملاً هذا الشهر.
RTLركّز على تبويب وجرّب الأسهم، معناها ينقلب مع الاتجاه

تشريح العنصر: كل جزء واسمه

مرّر على أي سطر ليتحدّد مكانه في الديمو فوق.

برومبت البناء

ابنِ مجموعة تبويبات على نمط ARIA. الحاوية عنصر بـ role="tablist" و aria-label واضح، وكل تبويب <button type="button" role="tab"> له id و aria-controls يشير إلى لوحه، مع aria-selected="true" على واحد فقط و"false" صراحةً على الباقي. طبّق roving tabindex: صفر للمختار و‎-1 لغيره، وتنقّلاً بالأسهم مع الالتفاف، و Home و End لأوّل وآخر تبويب، واقلب معنى ArrowLeft و ArrowRight عندما يكون اتجاه الحاوية rtl بقراءة getComputedStyle(list).direction وقت التنفيذ لا من لغة الواجهة. اجعل التفعيل يدوياً (Enter أو Space) إن كان اللوح يجلب بيانات، وتلقائياً فيما عدا ذلك. كل لوح role="tabpanel" مع aria-labelledby يشير لتبويبه و tabindex="0" إن خلا من عنصر قابل للتركيز، والألواح غير النشطة تُخفى بـ hidden لا بصرياً فقط. احسب موضع الخطّ النشط من البداية المنطقية بـ getBoundingClientRect لا بـ offsetLeft، وحرّكه بـ inset-inline-start أو بـ translateX مضروباً في متغيّر ‎--dir. عند الفيض مرّر الشريط أفقياً بـ overflow-x: auto مع scroll-padding-inline-start، ونادِ scrollIntoView({ inline: "nearest" }) على التبويب المختار عند أوّل رسم وعند كل تغيير، وضع تدرّجَي التلاشي بـ inset-inline-start و inset-inline-end مع قلب زاوية linear-gradient داخل [dir="rtl"]. استخدم الخصائص المنطقية في كل التباعد والحدود (padding-inline، margin-inline، border-inline-start) ولا تكتب left أو right في أي مكان. واحفظ التبويب النشط في الـ URL حتى ينجو من التحديث ومن مشاركة الرابط.

سلوكه في الاتجاه من اليمين لليسار

الجزء ده حصري عندنا.

مفاتيح الأسهم تتبادل

ينعكس

في الشريط الأفقي ينصّ نمط 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" في العربية، وأيقونات التبويبات المحايدة (ترس الإعدادات، السلّة، الجرس) لا تُقلب.

أسماؤه في الكود

كل سطر هو كلمة مكتبة واحدة عن نفس الشيء. خد السطر اللي بيكلّم مشروعك.

ARIArole="tablist" + aria-selectedتبويب واحد فقط selected، والبقية aria-selected="false" صراحةً.
HTML<button type="button" role="tab" aria-controls>التبويب زرّ لا رابط؛ ما يغيّر الـ URL يبقى <a href> داخل <nav>.
Radix UI<Tabs.Root activationMode="manual">المفتاح الذي يحوّل السلوك من التفعيل التلقائي بالسهم إلى التفعيل بـ Enter.
shadcn/ui<Tabs><TabsList><TabsTrigger value>مبني فوق Radix، وقيمة value هي التي تزاوج كل زرّ بلوحه في TabsContent.
Material UI<Tabs variant="scrollable" scrollButtons>ماتيريال تتولّى شريط الفيض وعنصر المؤشّر المنزلق نيابةً عنك.
Ant Design<Tabs items={items} tabPosition="left" />التبويبات تُمرَّر كمصفوفة items، و tabPosition يسمّى بأسماء فيزيائية (left/right) لا منطقية.
CSS:dir(rtl)محدِّد يقرأ اتجاه العنصر نفسه، فتقلب به زاوية linear-gradient دون تعليق القاعدة على لغة الصفحة.

شوف كمان

آخر تحديث · 2026-08-19