Przegląd
Używaj natywnego elementu <dialog> w przypadku modalnych i niemodalnych okien dialogowych. Zapewnia semantykę ułatwień dostępu, obsługę zaznaczenia, renderowanie warstwy górnej i obsługę Esc, które niestandardowe nakładki muszą odtwarzać ręcznie.
Wybierz typ nakładki
Wybierz pierwszą pasującą regułę. Jeśli nie masz pewności, użyj reguły 1.
Reguła 1. Nakładka przerywa użytkownikowi i blokuje stronę (domyślnie)
Używaj ich w przypadku potwierdzeń, formularzy, logowania, działań destrukcyjnych oraz wszelkich żądań „modalnych”, „okien dialogowych”, „lightboxów” lub „wyskakujących okienek”, które nie zawierają innych informacji.
Użyj elementu <dialog> otwartego jako modal, deklaratywnie z command="show-modal" (patrz Deklaratywne okno i kontrola wyskakujących okienek) lub ze skryptu z 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>
Przeglądarka zapewnia: stronę obojętną, utrzymanie zaznaczenia, warstwę górną, ::backdrop, Esc i odrzucenie gestem wstecz oraz powrót zaznaczenia do elementu wywołującego.
- NIE dodawaj
role="dialog",aria-modal, pułapki fokusu, przełączaniainertani modułu obsługiEsc. - NIE otwieraj go za pomocą atrybutu
show()aniopen. - ZATRZYMAJ przewijanie strony za pomocą poniższego kodu CSS. Więcej informacji znajdziesz w artykule Zapobieganie przewijaniu strony pod oknem modalnym.
- NIE blokuj przewijania za pomocą
overflow: hiddenaniposition: fixedwhtmllubbody.
/* 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;
}
Nie dotyczy menu ani etykietek – patrz reguła 3.
Reguła 2. Użytkownik kontynuuje pracę na stronie, gdy panel jest otwarty
Używaj w przypadku pasków wyszukiwania, inspektorów, palet i paneli tworzenia, które semantycznie nadal są oknami.
Użyj <dialog popover> w przypadku panelu najwyższej warstwy z możliwością zamknięcia wyskakującego okienka (auto umożliwia zamknięcie przez kliknięcie poza okienkiem, manual nie). Używaj dialog.show() tylko wtedy, gdy panel nie może znajdować się w górnej warstwie.
<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>
Strona pozostaje interaktywna i nie jest nieaktywna.
- DO include a visible close button.
- NIE używaj tagu
showModal(), który blokuje stronę.
Nie do blokowania potwierdzeń: patrz reguła 1.
Reguła 3. Nakładka nie jest oknem dialogowym
Używaj w menu, menu, etykietkach, kartach informacyjnych i powiadomieniach.
Użyj elementu semantycznego z atrybutem popover, a nie <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
Wybierz sposób wyłączania
Domyślna wartość zależy od sposobu otwarcia okna. Jeśli parametr closedby jest nieobecny lub nieprawidłowy, showModal() działa jak closerequest, a każdy inny sposób otwierania działa jak none.
- Tylko klawisz Esc i gest wstecz (domyślny tryb modalny): nie ustawiaj niczego lub ustaw
closedby="closerequest". - Kliknięcie poza obszarem powoduje zamknięcie: użyj
closedby="any". - Tylko deweloper może zamknąć okno (np. formularz z niezapisanymi zmianami): użyj
closedby="none"i dodaj ścieżkę potwierdzenia. Zobacz sekcję „Zamykanie” poniżej.
Wytyczne
Nazwa na potrzeby ułatwień dostępu i początkowy fokus
- DO name every dialog with
aria-labelledbypointing at its heading, oraria-labelwhen there is no visible title. - UMIEŚĆ
autofocusna głównym elemencie sterującym lub w pierwszym polu. - UMIEŚĆ
autofocusna bezpiecznym działaniu (np. Anuluj lub Zachowaj) w potwierdzeniu destrukcyjnym. - NIE pisz kodu, który po zamknięciu przywraca zaznaczenie. Przeglądarka zwraca go do elementu wywołującego.
Otwieranie
- ZALECENIE: otwieraj deklaratywnie za pomocą
command="show-modal"icommandforw<button>. Więcej informacji znajdziesz w artykule Declarative Dialog and Popover Control (w języku angielskim). - UŻYWAJ
dialog.showModal()podczas otwierania ze skryptu.
Zakończenie
- ZAMYKAJ za pomocą przycisków
<form method="dialog">lubcommand="close". - WYBIERZ
closedbycelowo:any(lekkie odrzucenie iEsc),closerequest(tylkoEsc, domyślny tryb modalny) lubnone(tylko zamknięcie kontrolowane przez dewelopera). - UŻYWAJ
command="request-close"lubrequestClose()w przypadku przycisków Anuluj, które muszą uwzględniać logikę niezapisanych zmian. Najpierw uruchamiającanceli ignorująclosedby. - TRAKTUJ
closedbyjako progresywne ulepszanie.Esci gest wstecz na urządzeniach mobilnych już zamykają okna bez tego przycisku.
Podłączanie do zamknięcia
- DO read
dialog.returnValuein acloselistener to learn which button was used, then reset it withdialog.returnValue = ''.Esci inne żądania zamknięcia nie ustawiają żadnej wartości, a przeglądarki różnią się pod względem tego, czy czyszczą poprzednią wartość, więc bez resetowania może dojść do wycieku nieaktualnej wartości. Traktuj''jako „odrzucono bez wyboru”. - UŻYWAJ
canceldo przechwytywaniaEscirequest-close, ale nie polegaj napreventDefault()w przypadku wielokrotnego naciskaniaEscbez aktywacji użytkownika. - ZALECANE: używaj
closedby="none"z wyraźnym krokiem potwierdzenia w przypadku formularzy z niezapisanymi zmianami. - POINFORMUJ użytkownika, dlaczego okno pozostało otwarte, gdy uniemożliwiono wykonanie działania
cancel: wyświetl widoczny komunikat i odwołaj się do niego za pomocą atrybutuaria-describedbyz elementu sterującego, który jest zaznaczony (np. Odrzuć), aby czytnik ekranu odczytał go, gdy zaznaczenie zostanie przeniesione. Bez niego przeniesienie zaznaczenia na nowy przycisk powoduje odczytanie tylko nazwy tego przycisku. - NIE pozwól, aby potwierdzenie przesunęło elementy sterujące pod wskaźnik. Zarezerwuj miejsce na komunikat (ukryj go za pomocą
visibility, a niedisplaylubhidden) i dodaj przycisk destrukcyjny w miejscu, w którym istniejące przyciski nie będą się przesuwać (np. jako pierwszy w wierszu wyrównanym do prawej), aby drugie kliknięcie w tym samym miejscu nie spowodowało odrzucenia zmian wprowadzonych przez użytkownika.
Okna niemodalne
- UŻYWAJ
<dialog popover>(autolubmanual) w przypadku panelu górnej warstwy idialog.show(), gdy nie chcesz górnej warstwy. - DO include a visible close button in every non-modal dialog.
Tło i styl
- DO style
::backdropand usedialog:modalto target modal-only styles. - DO leave animation to Animate Elements To and From Top Layer, dragging to Build a Draggable Element, and reparenting to Persistent Top Layer UI.
- NIE ustawiaj
displaynadialogbezwarunkowo. Zastępuje stan zamknięty. Zakres:dialog[open].
Ułatwienia dostępu
- NIE dodawaj do natywnego okna modalnego elementów
role="dialog",aria-modal, ręcznej pułapki ostrości ani przełączaniainert. - UŻYWAJ natywnego elementu
<button>z nazwą na potrzeby ułatwień dostępu do elementów sterujących zamykaniem. - UŻYWAJ jednego spójnego poziomu nagłówka w tytułach okien.
Strategie zastępcze
closedby (dialog-closedby)
Bez closedby okno nadal zamyka się po naciśnięciu Esc i wykonaniu gestu wstecz. Nie działa tylko kliknięcie poza oknem, więc jest to łagodna degradacja. closedby="none" jest ignorowany w ten sam sposób: Esc uruchamia cancel, a następnie zamyka okno. Zabezpieczenie przed niezapisanymi zmianami powinno być obsługiwane przez cancel, a nie tylko przez closedby="none", aby chronić również tam, gdzie brakuje closedby. Jeśli Twój cel Baseline wymaga lekkiego odrzucenia, dodaj ten element tam, gdzie brakuje 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
Traktuj izolację przewijania jako progresywne ulepszanie. Nie wykrywaj funkcji: @supports zgłasza właściwość jako obsługiwaną nawet wtedy, gdy kontenery bez możliwości przewijania ją ignorują. Jeśli nie jest to wymuszane, okno nadal zawiera pasek przewijania, gdy jego zawartość jest zbyt długa, a element showModal() nadal sprawia, że strona jest nieaktywna. Nie używaj overflow: hidden ani position: fixed w html lub body, ponieważ powoduje to usunięcie głównego paska przewijania i przesunięcie układu.
Polecenia wywołujące (command/commandfor)
Jeśli Twój cel podstawowy nie obejmuje poleceń wywołujących, zachowaj te same znaczniki i warunkowo wczytuj polyfill (te same wrażenia, co w przypadku progresywnego ulepszania). Wykrywanie w interfejsie i nigdy nie wczytywanie bezwarunkowo:
// 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
}
Tylko reguły 2 i 3 używają wyskakującego okienka.
Kreacje zastępcze i obsługa wyskakujących okienek w przeglądarkach
Interfejs Popover API to głównie progresywne ulepszanie, ale jego definiujące zachowania – promowanie warstwy górnej, lekkie odrzucanie i wywoływanie popovertarget – nie mają odpowiednika tylko w CSS. Starsze przeglądarki wymagają polyfilla lub ręcznego rozwiązania zastępczego, jeśli nie chcesz go dostarczać.
Polyfill: aby obsługiwać atrybut popover w starszych przeglądarkach, warunkowo wczytuj @oddbird/popover-polyfill. OBOWIĄZKOWO: wykryj funkcję, sprawdzając właściwość popover w HTMLElement.prototype, i wczytaj polyfill tylko wtedy, gdy brakuje obsługi natywnej – NIE wczytuj go bezwarunkowo.
Za pomocą bundlera lub mapy importu:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Bez bundlera, importuj z sieci CDN w tagu <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Uwaga dotycząca stylów: polyfill nie może zdefiniować prawdziwej pseudoklasy :popover-open, więc zamiast tego stosuje klasę .\:popover-open. OBOWIĄZKOWO: połącz te 2 właściwości za pomocą :is() lub :where(), w przeciwnym razie przeglądarki, które nie obsługują :popover-open, odrzucą całą regułę:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Możesz też użyć position: fixed i ręcznie obliczyć współrzędne za pomocą getBoundingClientRect() lub polegać na domyślnym pozycjonowaniu za pomocą inset: auto, jeśli jest to akceptowalne w danym przypadku użycia.