UIDex

ترقيم الصفحات

Pagination

<nav aria-label="pagination">

اسمه كمانباچينيشن، باجينيشن، شريط الصفحات، أرقام الصفحات، التنقّل بين الصفحات، مؤشّر النقاط، pager، paging، page navigation، paginator، page numbers، dot indicator

ترقيم الصفحات منطقة تنقّل تقسّم مجموعة واحدة كبيرة (نتائج بحث، جدول طلبات، أرشيف مقالات) إلى شرائح متساوية، وتمنح كل شريحة عنواناً خاصاً بها. والفائدة في هذا العنوان: الصفحة السابعة لها رابط مثل ‎?page=7 تنسخه وترسله لزميلك، وتحفظه في المفضّلة، ويفهرسه محرّك البحث، وتعود إليه بعد أسبوع فتجد نفس الموضع. وهو ليس مؤشّر خطوات، لأن الخطوات رحلة مرتّبة لها نهاية تُنجَز عندها المهمّة، أمّا الصفحات فشرائح متكافئة تقفز بينها بأي ترتيب ولا شيء «يكتمل» في آخرها. والتمرير اللانهائي (infinite scroll) شيء آخر، فهو يلغي من الأساس فكرة الموضع الذي يملك عنواناً: لا رقم ولا رابط ولا طريق للرجوع إلى نتيجة رأيتها أمس إلا بإعادة التمرير من البداية. أمّا زرّ «تحميل المزيد» فالفرق بينه وبين الترقيم ميكانيكي لا ذوقي: الزرّ يُلحِق الدفعة الجديدة أسفل القديمة فينمو الـ DOM بلا سقف، بينما ترقيم الصفحات يستبدل المحتوى استبدالاً كاملاً فيبقى حجم الصفحة ثابتاً مهما تعمّق المستخدم. والقاعدة العملية: لو احتاج المستخدم يوماً أن يقول «أنا في الصفحة الرابعة» أو أن يعود إليها لاحقاً، فالترقيم وحده من بين الثلاثة يقدر على ذلك.

لو قلت عليه…

«الأرقام اللي تحت الجدول»«١ ٢ ٣ اللي مكتوبين تحت نتايج البحث»«زراير السابق والتالي بتاعة الصفحات»«النقط الصغيرة اللي تحت السلايدر»«الحتّة اللي بتوديني للصفحة اللي بعدها»

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

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

عرض ١٢١‎–‎١٤٠ من ٣٤٩صفحة ٧ من ١٨
نسخة الكاروسيل
RTLالصفحة ١ في اليمين، والسهمان ينعكسان، والأرقام لا تنعكس

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

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

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

ابنِ ترقيم صفحات لقائمة نتائج طويلة. اجعل رقم الصفحة مصدر الحقيقة الوحيد واقرأه من الـ URL (‎?page=) لا من حالة داخلية، حتى يعمل زرّ الرجوع في المتصفّح وتصلح مشاركة الرابط. لُفّ كل شيء في <nav aria-label> باسم فريد لكل مُرقِّم في الصفحة، وضع بداخله <ul> من العناصر. كل رقم <a href> حقيقي لا <button onClick>، والصفحة الحالية عنصر واحد يحمل aria-current="page" ولا يكون رابطاً وله تمييز غير لوني. أضف زرّي السابق والتالي باسمين مقروءين و rel="prev" و rel="next"، وعند الحدّ أسقط href وحوّلهما إلى <span aria-disabled="true"> بدل الاكتفاء بـ opacity. احسب نافذة عرض يثبت عدد خاناتها: الأولى، والأخيرة، وجارة حول الحالية. وضع بين النطاقين علامة حذف بـ aria-hidden="true" ومسافة بـ margin-inline. استخدم الخصائص المنطقية في كل مكان: gap و padding-inline و margin-inline و inset-inline-start، ولا تكتب margin-left ولا justify-content: left. اعكس أيقونتَي السهم بـ scaleX(-1) داخل [dir="rtl"] ولا تبدّل ترتيبهما في الـ DOM. لو أضفت تنقّلاً بمفاتيح الأسهم فاقرأ الاتجاه بـ ‎:dir(rtl)‎ واقلب إشارة الخطوة بدل ربط ArrowRight بالصفحة التالية. ولّد نصّ كل الأرقام من ‎Intl.NumberFormat‎ واحد وأبقِ القيمة المرسلة في الرابط لاتينية، وضع LRM‏ (U+200E) على جانبَي الشرطة في أي نطاق مثل «١٢١–١٤٠» لأن العزلة وحدها لا تمنع خوارزمية bidi من قلبه إلى «١٤٠–١٢١» مع الأرقام الهندية، وأضف font-variant-numeric: tabular-nums حتى لا يتغيّر عرض الصفّ بين ٩ و١٠. بعد كل انتقال أعد التركيز إلى بداية القائمة وأعلن الموضع الجديد في منطقة aria-live="polite". واعرض حالة فارغة صريحة لو طُلبت صفحة خارج النطاق بدل جدول فارغ صامت.

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

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

اتجاه صفّ الأرقام

ينعكس

الصفّ كله ينعكس: الصفحة ١ تجلس في أقصى اليمين، والأخيرة عند اليسار، وسهم «التالي» ينتهي على الطرف الأيسر. وحاوية flex-direction: row تفعل هذا وحدها لأن المحور المضمّن يتبع direction، فلا تضف row-reverse داخل [dir="rtl"] فهو انعكاس فوق انعكاس يعيد الصفحة ١ إلى اليسار. وللمحاذاة فخّ مشابه: justify-content: flex-start قيمة منطقية تتبع الاتجاه، بينما justify-content: left قيمة فيزيائية تلتصق بالحافّة اليسرى في اللغتين.

سهما السابق والتالي

ينعكس

السهمان أيقونتان اتجاهيتان بالمعنى الحرفي: «السابق» يجب أن يشير يميناً في العربية و«التالي» يساراً، لأنهما يشيران إلى موضع الصفحة على الشاشة لا إلى معنى مجرّد. أيقونة SVG لا تعرف شيئاً عن bidi فاعكسها صراحة بـ ‎[dir="rtl"] .chevron { transform: scaleX(-1) }‎. والخطأ المقابل أسوأ: لا تبدّل ترتيب الزرّين في الـ DOM، فالانعكاس بصري تتكفّل به direction، وتبديل الترتيب يدوياً يقلبهما مرّتين فيعودان لمكانهما ويصير Tab يمرّ على «التالي» قبل «السابق».

الأرقام والفاصل بينها

لا ينعكس

محارف الأرقام نفسها لا تنعكس أبداً، لكن المحرف المحايد بينها ينعكس: داخل فقرة عربية تأخذ الشرطة في «١٢١–١٤٠» اتجاه ما حولها فيُعرض النطاق «١٤٠–١٢١»، ويقرأ المستخدم عدّاً مقلوباً في سطر «عرض ١٢١–١٤٠ من ٣٤٩». و<bdi> لا ينقذك هنا إلا مع الأرقام اللاتينية، لأن القاعدة W7 تُعيد تصنيفها حرفاً قوياً داخل عزلة لاتينية، أمّا الأرقام الهندية فصنفها AN ولا تغيّره العزلة فيبقى النطاق مقلوباً؛ فضع LRM‏ (U+200E) على جانبَي الشرطة، أو ‎unicode-bidi: bidi-override‎ مع ‎direction: ltr‎ على النطاق وحده، أو اكتبها «من ١٢١ إلى ١٤٠» فتتخلّص من المحايد أصلاً. واختر نظاماً رقمياً واحداً للمنطقة كلها، هندياً ١ ٢ ٣ أو لاتينياً 1 2 3، ولا تكتب أبداً «صفحة ٣ من 18». وتبقى القيمة في الرابط لاتينية دائماً: ‎?page=٣ ليس رقماً عند الخادم و Number("٣") تُرجع NaN، فنسّق النصّ المعروض بـ ‎Intl.NumberFormat("ar-EG")‎ وأرسل القيمة كما هي.

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

ينعكس

حدث keydown يسلّمك مفتاحاً فيزيائياً: ArrowRight يبقى ArrowRight مهما كان اتجاه الصفحة، والمتصفّح لا يعكسه نيابةً عنك. لكن المستخدم العربي ينتظر أن يأخذه السهم الأيمن إلى الصفحة السابقة والأيسر إلى التالية، على عكس الإنجليزية تماماً. وهذا ما توجبه مواصفة WAI-ARIA لكل عنصر مركّب في RTL. اقرأ الاتجاه من العنصر نفسه بـ ‎el.matches(":dir(rtl)")‎ أو ‎getComputedStyle(el).direction‎، واضرب مقدار الخطوة في ‎-1‎ عند rtl، ولا تربط «الصفحة التالية» بـ ArrowRight ربطاً مباشراً في الكود.

نقاط الكاروسيل والتمرير

ينعكس

صفّ النقاط ينعكس مثل الأرقام: نقطة الشريحة الأولى في اليمين. والتخطيط سليم، لكن الحساب هو الذي ينكسر: داخل حاوية تمرير RTL تبدأ scrollLeft من صفر عند أقصى اليمين وتصير سالبة كلما تقدّمت، فمعادلة ‎Math.round(el.scrollLeft / slideWidth)‎ تعطي فهرساً سالباً وتُضيء النقطة الخطأ؛ صوابها ‎Math.round(Math.abs(el.scrollLeft) / slideWidth)‎. وللانتقال استخدم ‎slide.scrollIntoView({ inline: "start" })‎ أو scroll-snap-align: start فكلاهما منطقي ويتبع الاتجاه، بخلاف شريط يُحرَّك بـ ‎translateX(-100% * i)‎ الذي يجب أن تنقلب إشارته في RTL.

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

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

ARIAaria-current="page"العلامة الوحيدة التي تقول أي صفحة نحن فيها، ولا يحملها إلا عنصر واحد.
HTML<nav aria-label> + <ul>منطقة تنقّل مسمّاة حول قائمة؛ <ul> تكفي لأن الأرقام الظاهرة تحمل ترتيبها.
HTMLrel="prev" / rel="next"علاقتان صالحتان بين الروابط؛ توقّفت جوجل عن استخدامهما في الفهرسة سنة ٢٠١٩، فهما إشارة لبقية الوكلاء لا أداة SEO.
shadcn/ui<Pagination><PaginationContent>…أجزاء مركّبة: PaginationItem و PaginationLink مع isActive الذي يضبط aria-current، و PaginationEllipsis.
Material UI<Pagination count={18} siblingCount={1} />نافذة الاقتطاع تُضبط بـ siblingCount و boundaryCount، و renderItem هو المكان الذي تحقن فيه href حقيقياً.
Ant Design<Pagination total={349} showSizeChanger />يضيف مُبدّل حجم الصفحة وعدّاد الإجمالي، وهما ما تنساه أغلب النسخ المكتوبة باليد.
CSSmargin-inlineالمسافة المنطقية حول علامة الحذف، تنعكس وحدها بدل margin-left الثابتة.

شوف كمان

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