סקירה כללית
משתמשים ברכיב <dialog> המקורי לתיבות דו-שיח מודאליות ולא מודאליות. הוא מספק את הסמנטיקה של הנגישות, הטיפול במיקוד, הרינדור של השכבה העליונה והטיפול ב-Esc ששכבות-על בהתאמה אישית צריכות לבנות מחדש באופן ידני.
בוחרים את סוג שכבת-העל
בוחרים את הכלל הראשון שתואם. אם אתם לא בטוחים, השתמשו בכלל 1.
כלל 1: שכבת-העל מפריעה למשתמש וחוסמת את הדף (ברירת מחדל)
השתמשו בזה לאישורים, טפסים, כניסה, פעולות הרסניות וכל בקשה מסוג "מודאלי", "תיבת דו-שיח", "תיבת lightbox" או "חלון קופץ" שלא מצוין בה אחרת.
משתמשים ב-<dialog> שנפתח כמודל, באופן הצהרתי עם command="show-modal" (ראו תיבת דו-שיח הצהרתית ופקד Popover) או מסקריפט עם 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>. אפשר לקרוא מידע נוסף במאמר Declarative Dialog and Popover Control. - מומלץ להשתמש ב-
dialog.showModal()כשפותחים מסקריפט.
סגירה
- כן סוגרים באמצעות הלחצנים
<form method="dialog">אוcommand="close". - מומלץ לבחור את
closedbyבאופן מכוון:any(סגירה בלחיצה מחוץ למודל ו-Esc),closerequest(Escבלבד, ברירת המחדל של המודל) אוnone(סגירה בשליטת המפתח בלבד). - כן משתמשים ב-
command="request-close"או ב-requestClose()בלחצני ביטול שצריכים לפעול לפי הלוגיקה של שינויים שלא נשמרו. הם יורים קודם בcancelומתעלמים מclosedby. - כדאי להתייחס ל-
closedbyכשיפור הדרגתי (progressive enhancement).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 ותנועת החזרה. רק הלחיצה מחוץ למודל לא עובדת, אז זו הפחתה חיננית (graceful degradation). המערכת מתעלמת מ-closedby="none" באותו אופן: Esc מפעיל את cancel ואז סוגר את תיבת הדו-שיח. כדאי להשאיר את ההגנה על שינויים שלא נשמרו ב-handler של 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
התייחסו להכלת הגלילה כשיפור הדרגתי (progressive enhancement). אל תזהו את התכונה: @supports מדווח על המאפיין כנתמך גם במקרים שבהם מאגרים שלא ניתנים לגלילה מתעלמים ממנו. במקרים שבהם המאפיין לא נאכף, תיבת הדו-שיח עדיין מכילה גלילה בכל פעם שהתוכן שלה חורג, והמאפיין showModal() עדיין הופך את הדף ללא פעיל. אל תשתמשו ב-overflow: hidden או ב-position: fixed כחלופה ב-html או ב-body, כי זה מסיר את סרגל הגלילה הבסיסי ומשנה את הפריסה.
פקודות להפעלה (command / commandfor)
אם יעד ה-Baseline שלכם לא כולל פקודות Invoker, אתם יכולים להשאיר את אותו תיוג ולהטעין את ה-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 הוא בעיקר שיפור הדרגתי (progressive enhancement), אבל להתנהגויות שמגדירות אותו – קידום לשכבה העליונה, סגירה בלחיצה מחוץ לאלמנט והפעלה של popovertarget – אין מקבילה ב-CSS בלבד. בדפדפנים ישנים יותר צריך להשתמש ב-polyfill, או להגדיר חלופה ידנית אם אתם מעדיפים לא להשתמש ב-polyfill.
Polyfill: כדי לתמוך במאפיין popover בדפדפנים ישנים יותר, צריך לטעון את @oddbird/popover-polyfill באופן מותנה. חובה: צריך לזהות את התכונה על ידי בדיקת המאפיין popover ב-HTMLElement.prototype, ולטעון את ה-polyfill רק כשאין תמיכה מקומית – אל תטענו אותו ללא תנאי.
עם bundler או מפת ייבוא:
// 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;
}
לחלופין, כדי להשתמש ב-fallback מדור קודם בלי polyfill, אפשר להשתמש ב-position: fixed ולחשב קואורדינטות באופן ידני באמצעות getBoundingClientRect() או להסתמך על מיקום ברירת המחדל עם inset: auto אם זה מתאים לתרחיש לדוגמה.