UIDex

القائمة المنسدلة

Dropdown / Select

<select> / role="listbox"

اسمه كماندروب داون، سيلكت، قائمة الاختيار، اللائحة المنسدلة، حقل الاختيار، منسدلة، select، select box، picker، option list، listbox، select menu

القائمة المنسدلة زرّ يعرض قيمة واحدة مختارة، وعند فتحه تنبثق لائحة خيارات تُبدّل تلك القيمة ثم تُغلق. صفتها الجوهرية أنها تحمل قيمة (value): القيمة تبقى ظاهرة على الزرّ بعد الإغلاق، وتُرسَل مع النموذج، ويمكن قراءتها لاحقاً. ولا تخلطها بثلاثة أشياء تشبهها شكلاً. الأوّل الـ combobox القابل للكتابة، يضع حقل <input> محرّراً مكان الزرّ ويُصفّي اللائحة مع كل حرف ويعلن ذلك بـ aria-autocomplete="list". والفيصل بينهما ليس الدور، لأن ARIA 1.2 تُلبس المشغّلَين كليهما role="combobox"، بل هو وجود حقل كتابة من عدمه، ومن هنا بالضبط يأتي الخلط. والثاني القائمة (menu)، عنصر بـ role="menu" وبنوده role="menuitem" تُطلق أوامر مثل «نسخ» و«حذف» ولا تحتفظ بشيء، ولذلك لا وجود فيها لـ aria-selected أصلاً. والثالث الـ popover، مجرّد صندوق عائم بلا أي دلالة اختيار ولا علاقة ملكية بين حاويته وما بداخلها. والفيصل بجملة واحدة: لو الضغط على السطر ينفّذ فعلاً فهذه قائمة أوامر، ولو غيّر قيمة ستُقرأ لاحقاً فهذه قائمة اختيار. وداخل قوائم الاختيار نفسها يبقى فرق ثانٍ: الـ <select> الأصلي يرسمه النظام ولا تكاد تُنسَّق خياراته، لكنه يأتي بإمكانية وصول كاملة مجاناً، أمّا اللائحة المخصّصة (role="listbox") فتُنسّق كما تشاء وتتحمّل أنت كامل عبء لوحة المفاتيح و ARIA.

لو قلت عليه…

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

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

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

المدينة
القاهرة
الرياض
دبي
عمّان
RTLاللائحة تُحاذى ببداية المشغّل، والسهم لا ينعكس

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

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

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

ابنِ قائمة منسدلة تحمل قيمة واحدة (select) لا قائمة أوامر. المشغّل زرّ يعرض القيمة المختارة ويحمل role="combobox" و aria-haspopup="listbox" و aria-expanded يتبدّل فعلياً و aria-controls يشير إلى معرّف اللائحة، مع aria-labelledby يربطه بعنوان الحقل. اللائحة عنصر role="listbox" وأبناؤها role="option" لكلٍّ منها id ثابت و aria-selected صريح، وواحد فقط منها selected. أبقِ التركيز على المشغّل واستخدم aria-activedescendant للخيار المُبرَز، ومرّره داخل الرؤية بـ scrollIntoView({ block: "nearest" }). ادعم ArrowUp و ArrowDown و Home و End و Enter و Escape (يُغلق ويعيد التركيز) والكتابة السريعة للقفز بالحرف الأول. اجعل كل التموضع والتباعد بخصائص منطقية: inset-inline-start للمحاذاة، و padding-inline-start لمساحة علامة الصح، و text-align: start داخل الخيارات. ولا تعكس السهم في RTL، فهو يشير لأسفل ويدور 180 درجة عند الفتح فقط. أضف حقل input مخفياً باسم name ليصل الاختيار إلى النموذج، واقلب اللائحة لأعلى تلقائياً حين لا تتّسع الشاشة أسفلها.

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

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

محاذاة اللائحة

ينعكس

اللائحة تُحاذى بحافّة البداية المنطقية للمشغّل: يمينه في العربية، ويساره في الإنجليزية. فاكتبها inset-inline-start: 0 داخل حاوية position: relative بدل left: 0. ولو استعملت Floating UI فمحاذاة ‎-start و ‎-end فيها منطقية أصلاً وتقرأ direction من العنصر نفسه، فيكفي placement: "bottom-start" بلا أي شرط منك على الاتجاه. والفخّ الأكبر أن تُرسل اللائحة بـ portal إلى document.body: هناك تنقطع عن dir="rtl" الموروث من حاويتك العربية، فترتدّ محاذاة نصّها إلى اليسار، وتقرأ مكتبة التموضع الاتجاه ltr فتقلب المحاذاة كلها. عالجه بوضع dir على عنصر الـ portal نفسه أو على <html> مباشرة، لا على حاوية الصفحة وحدها.

السهم لا ينعكس

لا ينعكس

سهم القائمة يشير إلى أسفل، والأسفل ليس له يمين ولا يسار. فلا تضع عليه scaleX(-1) ولا rtl:-scale-x-100 كما تفعل مع سهم مسار التنقّل. الحركة الوحيدة المسموحة له هي rotate(180deg) عند الفتح. وفي الـ <select> الأصلي يرسم النظام السهم بنفسه ويضعه على الحافّة النهائية تلقائياً حين تكون direction: rtl، فإن ألغيته بـ appearance: none وجب تثبيت سهمك أنت بـ inset-inline-end لا right.

داخل الخيار

ينعكس

نصّ الخيار يُحاذى بـ text-align: start لا left، والمسافة المحجوزة لعلامة الصح تكون padding-inline-start. والفخّ الأشيع لائحة مختلطة مثل «الرياض» و«Riyadh (+966)»: لُفّ كل جزء لاتيني بـ <bdi> أو span بـ unicode-bidi: isolate، وإلا نقلت خوارزمية bidi علامة + والأقواس إلى الطرف الخطأ من السطر.

مفاتيح التنقّل الرأسية

لا ينعكس

ArrowUp و ArrowDown و Home و End لا تتأثّر باتجاه الصفحة إطلاقاً: Home أوّل خيار في اللائحة و End آخرها في العربية والإنجليزية سواء، لأن الترتيب هنا منطقي في الـ DOM لا بصري على الشاشة. لا تكتب شرطاً مثل if (dir === "rtl") لتبديلها، فهو يكسر توقّع المستخدم ويخالف نمط listbox في ARIA. وكذلك Escape يُغلق ويعيد التركيز إلى المشغّل بلا أي علاقة بالاتجاه.

الأسهم في اللائحة الأفقية

ينعكس

إن نفّذت لائحة أفقية بـ aria-orientation="horizontal" فإن ArrowRight و ArrowLeft تتبادلان المعنى: في RTL يقودك ArrowLeft إلى الخيار التالي و ArrowRight إلى السابق. اقرأ الاتجاه مرّة واحدة من getComputedStyle(el).direction، واضرب خطوة المؤشّر في ‎-1 عند rtl، بدلاً من ربط اسم المفتاح بالاتجاه مباشرة في كل مكان.

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

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

HTML<select><option value>العنصر الأصلي: إمكانية وصول كاملة مجاناً وواجهة نظام على الموبايل، مقابل خيارات لا تُنسَّق.
ARIArole="listbox" + aria-selectedالدور الذي يجعل اللائحة حاملة قيمة؛ غيابه يحوّلها إلى قائمة أوامر.
ARIArole="combobox" + aria-expanded + aria-controlsثلاثتها على المشغّل نفسه، حتى في النسخة غير القابلة للكتابة.
Radix UI<Select.Root> vs <DropdownMenu.Root>المكتبة تفصل الاثنين صراحةً: الأوّل يأخذ value و onValueChange، والثاني يأخذ onSelect.
shadcn/ui<Select> / <Command> comboboxالـ Select مبني على Radix، أمّا الـ combobox القابل للبحث فيُركَّب من Command داخل Popover.
Material UI<Select> vs <Autocomplete>Autocomplete هو الـ combobox الحقيقي بالكتابة والتصفية، و Select اختيار من لائحة ثابتة.
CSSinset-inline-startالمحاذاة المنطقية التي تجعل اللائحة تلتصق ببداية المشغّل في الاتجاهين.

شوف كمان

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