UIDex

الشريط الجانبي

Sidebar

<aside> / <nav>

اسمه كمانسايدبار، القائمة الجانبية، شريط التنقّل الجانبي، اللوح الجانبي، العمود الجانبي، قائمة لوحة التحكّم، side navigation، side nav، nav sidebar، navigation rail، app shell navigation، side menu

الشريط الجانبي عمود دائم يجلس على حافّة الصفحة في المحور المضمّن، ويحمل تنقّل التطبيق أو محتوى مساعداً يخصّ الشاشة المعروضة. وهو قطعة من تخطيط الصفحة لا طبقة فوقها: يقاسم المحتوى الشبكة نفسها، فإن أخذ ٢٤٠ بكسل نقصت من عرض منطقة المحتوى ٢٤٠ بكسل بالضبط. ولهذا لا يحتاج طبقة تعتيم ولا حصر تركيز، ولا يجد Escape فيه شيئاً يغلقه، لأن أقصى ما يفعله المستخدم أن يطويه. وهو ليس درجاً جانبياً (drawer): الدرج سطح مؤقّت ينزلق فوق المحتوى ويُغلق بالنقر خارجه أو بـ Escape، أمّا الشريط فيبقى ظاهراً بلا فعل من أحد. والشريط الضيّق (navigation rail) هو هذا العمود نفسه بعد أن ضاق إلى أيقونات وأُخفيت تسمياته، بنية DOM واحدة في حالة أخرى، حتى في الأنظمة التي تشحنه مكوّناً مستقلاً باسمه. وليس شريط تنقّل علويّاً (navbar)، فذاك يمتدّ أفقياً بعرض الصفحة ويضيق بعد خمسة أو ستّة روابط، بينما يستوعب الشريط الجانبي عشرات الروابط في مجموعات لأن المحور الرأسي يقبل التمرير. والاختبار العملي: لو أزاح ظهورُه المحتوى فهو شريط جانبي، ولو غطّاه فهو درج.

لو قلت عليه…

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

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

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

لوحة التحكّمشريط كامل

الشريط يقاسم هذه المنطقة الصفّ نفسه. طيّه يوسّع المحتوى ولا يكشف طبقة تحته.

RTLالعمود عند اليمين في العربية، وسهم الطيّ وحده هو ما ينعكس

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

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

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

ابنِ شريطاً جانبياً دائماً لتطبيق لوحة تحكّم. اجعله عموداً داخل تخطيط الصفحة نفسه عبر grid-template-columns: var(--sidebar-w) 1fr، لا طبقة فوق المحتوى، فلا طبقة تعتيم ولا حصر تركيز ولا إغلاق بـ Escape. لُفّ الروابط في <nav> يحمل aria-label يميّزه عن أي <nav> آخر في الصفحة، واستعمل <aside> بدلاً منه إن كان ما بداخله محتوى مساعداً لا روابط. رتّب الروابط في مجموعات، كل مجموعة <ul> مربوطة بعنوانها عبر aria-labelledby، وأي عنوان قابل للطيّ يكون زرّاً بـ aria-expanded. كل عنصر رابط يملأ صفّه بالحشو ويحوي أيقونة بـ aria-hidden="true" وتسمية نصّية. علّم الصفحة الحالية بـ aria-current="page" على الرابط نفسه وبعنصر واحد فقط، ولا تكتفِ باللون. أضف زرّ طيّ بـ aria-expanded واسم ثابت لا تدخله الحالة، واحفظ الاختيار في localStorage. وفي الحالة المطويّة أبقِ نصّ كل تسمية في الـ DOM داخل عنصر مخفيّ بصرياً أو ضع aria-label على الرابط، ولا تجعل التلميح بديلاً عن الاسم المقروء. اجعل قائمة الروابط وحدها هي ما يمرّر بـ overflow-y: auto مع min-block-size: 0، وأبقِ الترويسة وزرّ الطيّ وتذييل الحساب خارج منطقة التمرير، وقس الارتفاع بـ 100dvh لا 100vh. واستعمل الخصائص المنطقية في كل موضع: border-inline-end للفاصل، padding-inline-start لتدرّج العناصر المتداخلة، inset-inline-start للتثبيت، margin-inline-start لإزاحة المحتوى، ولا تكتب left ولا right ولا margin-left ولا padding-left. واجعل الطيّ يحرّك inline-size أو متغيّر عرض العمود مسجَّلاً بـ @property، لا translateX. واقلب سهم الطيّ وحده في RTL بصنف مخصّص، ولا تضع قاعدة عامة تقلب كل svg.

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

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

أي حافّة يجلس عليها

ينعكس

الشريط الذي يجلس على يسار الصفحة في الإنجليزية يجلس على يمينها في العربية، وينتقل وحده إذا كان التخطيط grid أو صفّ flex وكان العمود أول عنصر في ترتيب الـ DOM، لأن المحور المضمّن يتبع dir. فلا تكتب grid-template-columns: 240px 1fr ثم تعكسها بيدك داخل [dir="rtl"]، فالقيمة نفسها صحيحة في الاتجاهين ما دام المسار الأول هو البداية المنطقية. والانكسار الحقيقي يأتي من position: fixed مع left: 0 ومحتوى مزاح بـ margin-left: 240px، فهنا لا ينعكس شيء إطلاقاً، والصحيح inset-inline-start: 0 مع margin-inline-start على المحتوى.

الحدّ الفاصل عن المحتوى

ينعكس

الخطّ الذي يفصل الشريط عن المحتوى مرسوم على الحافّة المواجهة للمحتوى، أي border-inline-end لا border-right، ومع border-right يقفز الخطّ في العربية إلى الحافّة الخارجية للنافذة ويُترك التقاء الشريط بالمحتوى بلا فاصل. وشريط تمرير قائمة الروابط يقع على تلك الحافّة نفسها لأنه يلزم نهاية المحور المضمّن، فينتقل من اليمين إلى اليسار مع انقلاب الاتجاه، وأي padding-right أُضيف ليبعد النصّ عنه يبقى في الجهة الخطأ، والبديل padding-inline-end أو scrollbar-gutter: stable. وإن رسمت الفصل بظلّ بدل الحدّ فتذكّر أن box-shadow بلا صيغة منطقية، فاضرب إزاحته الأفقية في متغيّر اتجاه لينعكس معه.

تدرّج العناصر المتداخلة

ينعكس

العناصر الفرعية داخل مجموعة تُزاح عن حافّة البداية، أي إلى اليمين في العربية، والإزاحة padding-inline-start: 2rem على عنصر القائمة لا padding-left ولا margin-left. ولا تصنعها بمسافات فارغة ولا بـ text-indent موجب: المسافات تدخل في نصّ الرابط وتُقرأ معه، وtext-indent يزيح السطر الأول وحده فينكسر الشكل حين تلتفّ التسمية على سطرين. والخطّ الرأسي الذي يدلّ على التداخل مكانه inset-inline-start: 0 على المجموعة، ويُرسم بعنصر زائف حتى ينتقل معها إلى الجهة الأخرى.

سهم الطيّ وأيقونة الهامبرغر

ينعكس

سهم الطيّ أيقونة اتجاهية: هو يشير نحو الحافّة التي سينطوي العمود إليها، فينعكس مع الصفحة. أمّا أيقونة الهامبرغر فثلاثة خطوط أفقية متماثلة حول المحور الرأسي، وقلبها بـ scaleX(-1) لا يفيد شيئاً ما دامت الخطوط متساوية، ويعكس التصميم فعلاً متى اختلفت أطوالها. والقاعدة العملية: لا تكتب [dir="rtl"] svg { transform: scaleX(-1) } أبداً، بل ضع صنفاً مخصّصاً على الشيفرونات وأسهم الرجوع وحدها، واترك أيقونات الأقسام (البيت، المستخدم، الإعدادات، الجرس) كما هي.

حركة الطيّ

لا ينعكس

الطيّ تغيير في العرض لا إزاحة في المكان، والعرض لا جهة له فلا ينعكس منه شيء. ولو نفّذته بـ transform: translateX(-190px) ارتكبت خطأين معاً: خرج العمود من الحافّة الخطأ في العربية لأن التحويلات بلا صيغة منطقية وإزاحتها تحتاج الضرب في متغيّر اتجاه، وبقيت في التخطيط فجوة بعرضه كاملاً لأن transform لا يمسّ المساحة التي يشغلها العنصر. حرّك inline-size، أو انقل الانتقال إلى grid-template-columns مع إبقاء عدد المسارات نفسه في الحالتين حتى تُحسب القيم الوسيطة، أمّا متغيّر ‎--sidebar-w وحده فيقفز قفزاً ما لم تسجّله بـ @property بصيغة <length>.

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

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

HTML<nav aria-label="…"> / <aside>nav للروابط و aside للمحتوى المساعد، والاسم هو ما يفرّقه عن بقية معالم التنقّل.
ARIAaria-current="page"العلامة الوحيدة التي تُعلن الصفحة الحالية، واللون لا يصل إلى أحد.
ARIAaria-expanded="true|false"يحمل حالة الطيّ بدلاً من كتابتها داخل اسم الزرّ.
CSSborder-inline-end + padding-inline-startالفاصل عن المحتوى وتدرّج العناصر المتداخلة، وكلاهما ينعكس بلا قاعدة إضافية.
shadcn/ui<SidebarProvider> + useSidebar()يعطيك حالة الطيّ والشريط الضيّق جاهزة، ويحفظها في cookie حتى تعود كما تركتها.
Material UI<Drawer variant="permanent">لا يوجد في ماتيريال مكوّن باسم Sidebar، فالشريط الدائم هو نسخة من Drawer.
Ant Design<Layout.Sider collapsible collapsedWidth={80}>الطيّ وعرض الشريط الضيّق خاصّيتان جاهزتان، لكن الحفظ بين الصفحات يبقى عليك.

شوف كمان

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