Panoramica
Utilizza l'elemento <dialog> nativo per le finestre di dialogo modali e non modali. Fornisce la semantica di accessibilità, la gestione dello stato attivo, il rendering del livello superiore e la gestione di Esc che gli overlay personalizzati devono ricostruire manualmente.
Scegli il tipo di overlay
Scegli la prima regola corrispondente. In caso di dubbi, utilizza la Regola 1.
Regola 1: l'overlay interrompe l'utente e blocca la pagina (impostazione predefinita)
Utilizzalo per conferme, moduli, accesso, azioni distruttive e qualsiasi richiesta "modale", "finestra di dialogo", "lightbox" o "popup" che non indichi diversamente.
Utilizza <dialog> aperto come finestra modale, in modo dichiarativo con command="show-modal" (consulta Controllo dichiarativo di finestre di dialogo e popup) o dallo script con 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>
Il browser fornisce: pagina inerte, contenimento dello stato attivo, livello superiore, ::backdrop, Esc e chiusura con gesto Indietro e ritorno dello stato attivo all'invoker.
- NON aggiungere
role="dialog",aria-modal, un focus trap, l'attivazione/disattivazione diinerto un gestoreEsc. - NON aprirlo con
show()o con l'attributoopen. - SÌ: interrompi lo scorrimento della pagina dietro l'annuncio con il CSS riportato di seguito. Per maggiori dettagli, consulta Impedire lo scorrimento della pagina sotto una finestra di dialogo modale.
- NON bloccare lo scorrimento con
overflow: hiddenoposition: fixedsuhtmlobody.
/* 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;
}
Non per menu o tooltip: vedi la Regola 3.
Regola 2: l'utente continua a lavorare nella pagina mentre il riquadro è aperto
Utilizza questo ruolo per barre di ricerca, strumenti di controllo, tavolozze e riquadri di composizione che sono ancora finestre di dialogo dal punto di vista semantico.
Utilizza <dialog popover> per un riquadro di livello superiore con chiusura del popover (auto chiusura leggera, manual no). Utilizza dialog.show() solo quando il riquadro non deve trovarsi nel livello superiore.
<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>
La pagina rimane interattiva e non viene resa inerte.
- SÌ: includi un pulsante di chiusura visibile.
- NON utilizzare
showModal(), che blocca la pagina.
Non per bloccare le conferme: vedi la Regola 1.
Regola 3: l'overlay non è una finestra di dialogo
Utilizzalo per menu, menu a discesa, descrizioni comando, schede popup e notifiche popup.
Utilizza un elemento semantico con l'attributo popover, non <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
Scegli come ignorarlo
L'impostazione predefinita dipende dal modo in cui viene aperta la finestra di dialogo. Se closedby è assente o non valido, showModal() si comporta come closerequest e qualsiasi altro modo di apertura si comporta come none.
- Solo Esc e gesto Indietro (impostazione predefinita modale): non impostare nulla o
closedby="closerequest". - Fai clic anche all'esterno per chiudere: utilizza
closedby="any". - Solo lo sviluppatore lo chiude (ad esempio un modulo con modifiche non salvate): utilizza
closedby="none"e aggiungi un percorso di conferma. Consulta la sezione Chiusura di seguito.
Linee guida
Nome accessibile e stato attivo iniziale
- SÌ: assegna un nome a ogni finestra di dialogo con
aria-labelledbyche rimanda alla relativa intestazione oaria-labelquando non è presente un titolo visibile. - SÌ: inserisci
autofocusnel controllo principale o nel primo campo. - SÌ: inserisci
autofocusnell'azione sicura (ad esempio Annulla o Mantieni) in una conferma distruttiva. - NON scrivere codice per riportare lo stato attivo alla chiusura. Il browser lo restituisce all'invoker.
Apertura
- SÌ: apri in modo dichiarativo con
command="show-modal"ecommandforsu un<button>. Consulta Controllo dichiarativo di finestre di dialogo e popover. - DO use
dialog.showModal()when opening from script.
Chiusura
- SÌ: chiudi con i pulsanti
<form method="dialog">ocommand="close". - SCEGLI
closedbydeliberatamente:any(chiusura semplice eEsc),closerequest(soloEsc, l'impostazione predefinita della finestra modale) onone(solo chiusura controllata dallo sviluppatore). - SÌ: utilizza
command="request-close"orequestClose()per i pulsanti Annulla che devono rispettare la logica delle modifiche non salvate. Attivano primacancele ignoranoclosedby. - DA FARE: considera
closedbycome un potenziamento progressivo.Esce il gesto Indietro sui dispositivi mobili chiudono già le finestre modali senza questo pulsante.
Hooking into close
- SÌ: leggi
dialog.returnValuein un listenercloseper sapere quale pulsante è stato utilizzato, quindi reimpostalo condialog.returnValue = ''.Esce altre richieste di chiusura non impostano alcun valore e i browser si comportano in modo diverso per quanto riguarda la cancellazione del valore precedente, pertanto senza la reimpostazione potrebbe trapelare un valore non aggiornato. Considera''come "ignorato senza scelta". - SÌ: utilizza
cancelper intercettareEscerequest-close, ma non fare affidamento supreventDefault()per pressioni ripetute diEscsenza attivazione utente. - SÌ: utilizza
closedby="none"con un passaggio di conferma esplicito per i moduli con modifiche non salvate. - SÌ: comunica all'utente il motivo per cui la finestra di dialogo è rimasta aperta quando
cancelè impedito. Mostra un messaggio visibile e fai riferimento conaria-describedbydal controllo che riceve lo stato attivo (ad esempio Ignora), in modo che uno screen reader lo legga quando lo stato attivo si sposta. Senza questa impostazione, quando lo stato attivo si sposta su un nuovo pulsante, viene annunciato solo il nome del pulsante. - NON consentire che la conferma sposti i controlli sotto il puntatore. Riserva lo spazio del messaggio (nascondilo con
visibility, non condisplayohidden) e aggiungi il pulsante distruttivo in un punto in cui i pulsanti esistenti non si spostano (ad esempio, il primo in una riga allineata a destra), in modo che un secondo clic nello stesso punto non possa annullare le modifiche dell'utente.
Finestre di dialogo non modali
- SÌ: utilizza
<dialog popover>(autoomanual) per un riquadro di livello superiore edialog.show()quando non vuoi il livello superiore. - SÌ: includi un pulsante di chiusura visibile in ogni finestra di dialogo non modale.
Sfondo e stile
- UTILIZZA lo stile
::backdropedialog:modalper scegliere come target gli stili solo modali. - DA FARE: lascia l'animazione a Animate Elements To and From Top Layer, il trascinamento a Build a Draggable Element e il reparenting a Persistent Top Layer UI.
- NON impostare
displaysudialogin modo incondizionato. Esegue l'override dello stato chiuso. Definisci l'ambito condialog[open].
Accessibilità
- NON aggiungere
role="dialog",aria-modal, un focus trap manuale oinertper passare a una finestra modale nativa. - SÌ: utilizza un
<button>nativo con un nome accessibile per i controlli di chiusura. - SÌ: utilizza un livello di intestazione coerente per i titoli delle finestre di dialogo.
Strategie di riserva
closedby (dialog-closedby)
Senza closedby, una finestra modale si chiude comunque con Esc e il gesto Indietro; viene persa solo l'azione di clic all'esterno, quindi si tratta di una riduzione controllata. closedby="none" viene ignorato allo stesso modo: Esc attiva cancel e poi chiude la finestra di dialogo. Mantieni la protezione per le modifiche non salvate in un gestore cancel, non solo in closedby="none", in modo che protegga anche i casi in cui manca closedby. Se il target di riferimento richiede la chiusura semplice, aggiungi questo attributo dove manca 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
Tratta il contenimento dello scorrimento come un potenziamento progressivo. Non eseguire il rilevamento delle funzionalità: @supports segnala la proprietà come supportata anche se i contenitori non scorrevoli la ignorano. Se non viene applicato, la finestra di dialogo contiene comunque lo scorrimento ogni volta che i suoi contenuti superano il limite e showModal() rende comunque la pagina inerte. Non eseguire il fallback a overflow: hidden o position: fixed su html o body, che rimuove la barra di scorrimento principale e sposta il layout.
Comandi Invoker (command/commandfor)
Se il tuo target Baseline non include i comandi di attivazione, mantieni lo stesso markup e carica il polyfill in modo condizionale (la stessa esperienza, come potenziamento progressivo). Esegui il rilevamento sull'interfaccia e non caricarla mai in modo incondizionato:
// 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
}
Solo le regole 2 e 3 utilizzano Popover.
Alternative e supporto del browser per Popover
L'API Popover è per lo più un potenziamento progressivo, ma i suoi comportamenti distintivi (promozione al livello superiore, chiusura con un clic e attivazione di popovertarget) non hanno un equivalente solo CSS. I browser meno recenti richiedono un polyfill o un fallback manuale se preferisci non fornirne uno.
Polyfill: per supportare l'attributo popover nei browser meno recenti, carica @oddbird/popover-polyfill in modo condizionale. OBBLIGATORIO: esegui il rilevamento delle funzionalità controllando la proprietà popover su HTMLElement.prototype e carica il polyfill solo quando manca il supporto nativo; NON caricarlo incondizionatamente.
Con un bundler o una mappa di importazione:
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Senza un bundler, importa da una CDN all'interno di un <script type="module">:
<script type="module">
if (!("popover" in HTMLElement.prototype)) {
import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
}
</script>
Avvertenza sullo stile: il polyfill non può definire la pseudo-classe :popover-open reale, quindi applica una classe .\:popover-open. OBBLIGATORIO: combina i due elementi con :is() o :where(), altrimenti i browser che non dispongono di :popover-open ignorano l'intera regola:
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
In alternativa, per un fallback legacy senza un polyfill, utilizza position: fixed e calcola manualmente le coordinate tramite getBoundingClientRect() o affidati al posizionamento predefinito con inset: auto se è accettabile per il caso d'uso.