نظرة عامة
استخدِم عنصر <dialog> الأصلي لمربّعات الحوار المشروطة وغير المشروطة. وتوفّر دلالات تسهيل الاستخدام ومعالجة التركيز وعرض الطبقة العليا ومعالجة Esc التي يجب إعادة إنشائها يدويًا في الأشكال المخصّصة.
اختيار نوع التراكب
اختَر القاعدة الأولى التي تتطابق معها. إذا لم تكن متأكدًا، استخدِم القاعدة 1.
القاعدة 1: تقاطع النافذة المنبثقة المستخدم وتحجب الصفحة (الإعداد التلقائي)
يُستخدَم لرسائل التأكيد والنماذج وتسجيل الدخول والإجراءات المدمّرة وأي طلب "مشروط" أو "مربّع حوار" أو "مربّع حوار" أو "نافذة منبثقة" لا ينص على خلاف ذلك.
استخدِم <dialog> المفتوح كنموذج، بشكل تعريفي مع command="show-modal" (راجِع مربّع الحوار التعريفي وعنصر التحكّم في النافذة المنبثقة) أو من نص برمجي باستخدام showModal().
<button type="button" command="show-modal" commandfor="confirm">Delete</button>
<!-- MANDATORY: no role="dialog" or aria-modal; the native modal already provides them -->
<dialog id="confirm" aria-labelledby="confirm-title">
<h2 id="confirm-title">Delete this item?</h2>
<form method="dialog">
<button value="cancel" autofocus>Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
يوفّر المتصفّح ما يلي: صفحة خاملة، واحتواء التركيز، والطبقة العليا، و::backdrop، وEsc، وإغلاق النافذة بإيماءة الرجوع، وإعادة التركيز إلى العنصر الذي استدعى النافذة.
- لا تضِف
role="dialog"أوaria-modalأو فخ التركيز أو تبديلinertأو معالجEsc. - لا تفتحها باستخدام
show()أو السمةopen. - يجب إيقاف الانتقال إلى أسفل الصفحة خلفها باستخدام CSS أدناه. لمعرفة التفاصيل، يُرجى الاطّلاع على المقالة منع الصفحة أسفل مربّع حوار مشروط من الانتقال للأسفل.
- لا تقفل الانتقال باستخدام
overflow: hiddenأوposition: fixedعلىhtmlأوbody.
/* MANDATORY (both rules): without them, scrolling over the backdrop or inside the dialog scrolls the page behind it */
dialog:modal {
overscroll-behavior: contain; /* stops scroll gestures in the dialog chaining to the page */
}
dialog::backdrop {
overflow: hidden; /* ::backdrop is not a scroll container by default, so it would ignore overscroll-behavior */
overscroll-behavior: contain;
}
لا يُستخدم في القوائم أو تلميحات الشاشة: راجِع القاعدة 3.
القاعدة 2: يواصل المستخدم العمل في الصفحة أثناء فتح اللوحة
يُستخدم لأشرطة البحث وأدوات الفحص واللوحات وأجزاء الإنشاء التي لا تزال مربعات حوار من الناحية الدلالية.
استخدِم <dialog popover> للوحة الطبقة العليا مع إغلاق النافذة المنبثقة (يتم إغلاق auto تلقائيًا، بينما لا يتم إغلاق manual). استخدِم dialog.show() فقط عندما لا تكون اللوحة في الطبقة العليا.
<button type="button" popovertarget="find">Find</button>
<dialog id="find" popover="manual" aria-labelledby="find-title">
<h2 id="find-title">Find</h2>
<input type="search" aria-label="Search">
<button type="button" popovertarget="find" popovertargetaction="hide">Close</button>
</dialog>
تظل الصفحة تفاعلية ولا تصبح خاملة.
- يجب تضمين زر إغلاق مرئي.
- لا تستخدِم
showModal()، لأنّها تحظر الصفحة.
لا تُستخدَم لحظر التأكيدات: راجِع القاعدة 1.
القاعدة 3: لا يُعدّ العنصر المضاف مربّع حوار
يُستخدم للقوائم والقوائم المنسدلة والتلميحات والبطاقات المنبثقة ورسائل الإعلام المؤقتة.
استخدِم عنصرًا دلاليًا مع السمة popover، وليس <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
اختيار طريقة إغلاق الإشعار
يعتمد الإعداد التلقائي على كيفية فتح مربّع الحوار. إذا لم يتم ضبط السمة closedby أو كانت غير صالحة، ستعمل السمة showModal() كـ closerequest، وستعمل أي طريقة أخرى للفتح كـ none.
- مفتاح Esc وإيماءة الرجوع فقط (الوضع التلقائي): لا تضبط أي شيء، أو اضبط
closedby="closerequest". - النقر خارج النافذة لإغلاقها: استخدِم
closedby="any". - يغلقها المطوّر فقط (على سبيل المثال، نموذج يتضمّن تغييرات غير محفوظة): استخدِم
closedby="none"وأضِف مسار تأكيد. يُرجى الاطّلاع على قسم "الخاتمة" أدناه.
الإرشادات
اسم العنصر الظاهر على واجهة المستخدم والتركيز الأوّلي
- يجب تسمية كل مربّع حوار باستخدام السمة
aria-labelledbyالتي تشير إلى عنوانه، أو السمةaria-labelفي حال عدم توفّر عنوان مرئي. - يجب وضع
autofocusعلى عنصر التحكّم الأساسي أو الحقل الأول. - يجب وضع
autofocusعلى الإجراء الآمن (مثل "إلغاء" أو "احتفاظ") في رسالة تأكيد الإجراء المدمّر. - لا تكتب رمزًا برمجيًا لإعادة التركيز عند الإغلاق. ويعيدها المتصفّح إلى المُستدعي.
فتح
- يجب فتح التطبيق بشكل تعريفي باستخدام
command="show-modal"وcommandforعلى<button>. يُرجى الاطّلاع على مربّع الحوار التعريفي وعنصر التحكّم في النافذة المنبثقة. - يجب استخدام
dialog.showModal()عند فتح التطبيق من نص برمجي.
الخاتمة
- يجب إغلاقها باستخدام أزرار
<form method="dialog">أوcommand="close". - يجب اختيار
closedbyبعناية:any(الإغلاق البسيط وEsc) أوcloserequest(Escفقط، وهو الإعداد التلقائي للنافذة المشروطة) أوnone(الإغلاق الذي يتحكّم فيه المطوّر فقط). - يجب استخدام
command="request-close"أوrequestClose()لأزرار الإلغاء التي يجب أن تلتزم بمنطق التغييرات غير المحفوظة. يتم إطلاقcancelأولاً وتجاهلclosedby. - يجب التعامل مع
closedbyعلى أنّه تحسين متدرّج. يؤدي استخدامEscوإيماءة الرجوع على الأجهزة الجوّالة إلى إغلاق النوافذ المشروطة بدون الحاجة إلى هذا الزر.
الربط بالإغلاق
- يجب قراءة
dialog.returnValueفي أداة استماعcloseلمعرفة الزر الذي تم استخدامه، ثم إعادة ضبطه باستخدامdialog.returnValue = ''. لا تضبطEscوالطلبات الأخرى المشابهة أي قيمة، وتختلف المتصفحات في ما إذا كانت تمحو القيمة السابقة، لذا بدون إعادة الضبط، يمكن أن تتسرّب قيمة قديمة. يتم التعامل مع''على أنّه "تم إغلاقه بدون اختيار". - يُرجى استخدام
cancelلاعتراضEscوrequest-close، ولكن لا تعتمد علىpreventDefault()للضغط المتكرر علىEscبدون تفعيل من المستخدم. - يجب استخدام
closedby="none"مع خطوة تأكيد صريحة للنماذج التي تم تعديلها. - يجب إخبار المستخدم بسبب بقاء مربّع الحوار مفتوحًا عند منع
cancel: اعرِض رسالة مرئية وأشِر إليها باستخدامaria-describedbyمن عنصر التحكّم الذي يتم التركيز عليه (مثل "تجاهل")، ليقرأها برنامج قراءة الشاشة عند انتقال التركيز. بدون ذلك، فإن التركيز الذي ينتقل إلى زر جديد يعلن فقط عن اسم هذا الزر. - لا تسمح لرسالة التأكيد بنقل عناصر التحكّم تحت المؤشر. احجز مساحة الرسالة (أخفِها باستخدام
visibility، وليسdisplayأوhidden) وأضِف الزر المدمِّر في مكان لا تتحرك فيه الأزرار الحالية (على سبيل المثال، أولاً في صف محاذٍ لليمين)، حتى لا تؤدي نقرة ثانية في المكان نفسه إلى تجاهل تغييرات المستخدم.
مربّعات الحوار غير المشروطة
- يجب استخدام
<dialog popover>(autoأوmanual) للوحة الطبقة العليا، وdialog.show()عندما لا تريد الطبقة العليا. - يجب تضمين زر إغلاق مرئي في كل مربّع حوار غير مشروط.
الخلفية والتصميم
- يجب تصميم
::backdropواستخدامdialog:modalلاستهداف الأنماط الخاصة بالنافذة المشروطة فقط. - يُرجى ترك الحركة على تحريك العناصر من الطبقة العليا وإليها، والسحب إلى إنشاء عنصر قابل للسحب، وإعادة تحديد العنصر الرئيسي إلى واجهة مستخدم الطبقة العليا الثابتة.
- لا تضبط السمة
displayعلىdialogبدون شروط. وتلغي هذه الحالة حالة الإغلاق. يمكنك تحديد نطاقها باستخدامdialog[open].
تسهيل الاستخدام
- لا تضِف
role="dialog"أوaria-modalأو فخ تركيز يدوي أوinertللتبديل إلى نافذة مشروطة أصلية. - يجب استخدام عنصر
<button>أصلي مع اسم ظاهر على واجهة المستخدم لعناصر التحكّم في الإغلاق. - يجب استخدام مستوى عنوان واحد متّسق لعناوين مربّعات الحوار.
استراتيجيات احتياطية
closedby (dialog-closedby)
بدون closedby، سيظل بالإمكان إغلاق النافذة المشروطة باستخدام Esc وإيماءة الرجوع، ولكن لن يكون بالإمكان إغلاقها بالنقر خارجها، لذا يُعدّ ذلك تكيّفًا مع الإصدارات الأقدم. يتم تجاهل closedby="none" بالطريقة نفسها: يطلق Esc cancel ثم يغلق مربّع الحوار. يجب الاحتفاظ بميزة الحماية من التغييرات غير المحفوظة في معالج cancel، وليس في closedby="none" وحده، لكي تحمي أيضًا الحالات التي لا يتوفّر فيها closedby. إذا كان هدفك الأساسي يتطلّب ميزة الإغلاق السريع، أضِف ما يلي في حال عدم توفّر closedby:
if (!('closedBy' in HTMLDialogElement.prototype)) {
// Test the pointer position, not event.target: the <dialog> is also the target for its own
// padding and the gaps between its children, so a target check would close on those clicks
const outside = (e, dialog) => {
const r = dialog.getBoundingClientRect();
return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
};
for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {
let pressedOutside = false;
dialog.addEventListener('pointerdown', (e) => { pressedOutside = outside(e, dialog); });
// Require the press and the release outside, so a text-selection drag that ends on the backdrop does not dismiss
dialog.addEventListener('pointerup', (e) => { if (pressedOutside && outside(e, dialog)) dialog.close(); });
}
}
overscroll-behavior
يجب التعامل مع احتواء الانتقال على أنّه تحسين تدريجي. Do not feature-detect it: @supports reports the property as supported even where non-scrollable containers ignore it. في حال عدم فرض ذلك، سيظل مربّع الحوار يحتوي على شريط تمرير كلما تجاوز محتواه الحدّ الأقصى، وسيظل showModal() يجعل الصفحة غير نشطة. لا تلجأ إلى استخدام overflow: hidden أو position: fixed في html أو body، لأنّ ذلك يؤدي إلى إزالة شريط التمرير الجذر وتغيير التنسيق.
أوامر Invoker (عددها: command / commandfor)
إذا كان هدفك الأساسي لا يتضمّن "أوامر المُشغِّل"، احتفِظ بالترميز نفسه وحمِّل الـ polyfill بشكل مشروط (التجربة نفسها، كتحسين تدريجي). اكتشِف الواجهة ولا تحمِّلها أبدًا بدون شروط:
// MANDATORY: feature detect on HTMLButtonElement.prototype and load only when native support is missing
if (!('commandForElement' in HTMLButtonElement.prototype)) {
import('invokers-polyfill'); // npm install invokers-polyfill
}
تستخدم القاعدتان 2 و3 فقط Popover.
الخيارات الاحتياطية وتوافق المتصفّح مع Popover
تعتمد واجهة Popover API في الغالب على التحسين التدريجي، ولكن سلوكياتها المحدّدة، مثل الترقية إلى الطبقة العليا والإغلاق البسيط واستدعاء popovertarget، ليس لها مكافئ في CSS فقط. تحتاج المتصفّحات القديمة إلى polyfill أو إلى إجراء احتياطي يدوي إذا كنت تفضّل عدم شحنها.
Polyfill: لإتاحة السمة popover في المتصفّحات القديمة، يمكنك تحميل @oddbird/popover-polyfill بشكل مشروط. إجراء إلزامي: يجب رصد الميزة من خلال التحقّق من السمة popover في HTMLElement.prototype، وتحميل polyfill فقط في حال عدم توفّر الدعم الأصلي، ويجب عدم تحميله بدون شروط.
باستخدام حزمة أو خريطة استيراد:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
بدون حزمة، يمكنك الاستيراد من شبكة توصيل محتوى (CDN) داخل <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
تحذير بشأن التنسيق: لا يمكن لـ polyfill تحديد الفئة الزائفة :popover-open الحقيقية، لذا يتم تطبيق فئة .\:popover-open بدلاً من ذلك. إجراء إلزامي: ادمج السمتَين مع :is() أو :where()، وإلا ستتجاهل المتصفحات التي لا تتضمّن :popover-open القاعدة بأكملها:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
بدلاً من ذلك، يمكنك استخدام position: fixed واحتساب الإحداثيات يدويًا من خلال getBoundingClientRect() أو الاعتماد على تحديد الموضع التلقائي باستخدام inset: auto إذا كان ذلك مقبولاً لحالة الاستخدام، وذلك كحل بديل قديم بدون polyfill.