Présentation
Utilisez l'élément <dialog> natif pour les boîtes de dialogue modales et non modales. Il fournit la sémantique d'accessibilité, la gestion du focus, l'affichage de la couche supérieure et la gestion de Esc que les superpositions personnalisées doivent recréer manuellement.
Sélectionner le type de superposition
Sélectionnez la première règle qui correspond. En cas de doute, appliquez la règle numéro 1.
Règle 1 : L'overlay interrompt l'utilisateur et bloque la page (par défaut)
Utilisez-le pour les confirmations, les formulaires, la connexion, les actions destructrices et toute demande de type "modal", "boîte de dialogue", "lightbox" ou "pop-up" qui n'indique pas le contraire.
Utilisez <dialog> ouvert en tant que modal, de manière déclarative avec command="show-modal" (voir Contrôle déclaratif des boîtes de dialogue et des popovers) ou à partir d'un script avec 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>
Le navigateur fournit les éléments suivants : page inerte, confinement du curseur, couche supérieure, ::backdrop, Esc et fermeture par geste de retour, et retour du curseur à l'appelant.
- N'AJOUTEZ PAS
role="dialog",aria-modal, un piège de sélection, un bouton d'activation/désactivationinertou un gestionnaireEsc. - À ÉVITER : Ouvrir le lien avec
show()ou l'attributopen. - À FAIRE : Empêchez la page de défiler derrière l'annonce avec le CSS ci-dessous. Pour en savoir plus, consultez Empêcher le défilement de la page sous une boîte de dialogue modale.
- À ÉVITER : Verrouiller le défilement avec
overflow: hiddenouposition: fixedsurhtmloubody.
/* 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;
}
Pas pour les menus ni les info-bulles : voir la règle 3.
Règle 2 : L'utilisateur continue de travailler sur la page pendant que le panneau est ouvert
À utiliser pour les barres de recherche, les inspecteurs, les palettes et les volets de composition qui sont toujours des boîtes de dialogue d'un point de vue sémantique.
Utilisez <dialog popover> pour un panneau de couche supérieure avec fermeture de popover (auto se ferme par simple clic, manual non). N'utilisez dialog.show() que lorsque le panneau ne doit pas se trouver dans la couche supérieure.
<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 page reste interactive et n'est pas rendue inerte.
- À FAIRE : Incluez un bouton de fermeture visible.
- N'UTILISEZ PAS
showModal(), qui bloque la page.
Pas pour bloquer les confirmations : voir la règle 1.
Règle 3 : La superposition n'est pas une boîte de dialogue
Utilisez-le pour les menus, les menus déroulants, les info-bulles, les fiches d'informations et les toasts.
Utilisez un élément sémantique avec l'attribut popover, et non <dialog>.
<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
<li><a href="/rename">Rename</a></li>
</ul>
Choisissez comment la fermer
La valeur par défaut dépend de la façon dont la boîte de dialogue est ouverte. Si closedby est absent ou non valide, showModal() se comporte comme closerequest, et toute autre méthode d'ouverture se comporte comme none.
- Geste Échap et Retour uniquement (valeur par défaut modale) : ne définissez rien ou définissez
closedby="closerequest". - Cliquer également à l'extérieur pour fermer : utilisez
closedby="any". - Seul le développeur peut la fermer (par exemple, un formulaire avec des modifications non enregistrées) : utilisez
closedby="none"et ajoutez un chemin de confirmation. Voir "Fermeture" ci-dessous.
Consignes
Nom accessible et sélection initiale
- À FAIRE : nommez chaque boîte de dialogue avec
aria-labelledbypointant vers son en-tête, ouaria-labellorsqu'il n'y a pas de titre visible. - À FAIRE : placez
autofocussur la commande principale ou le premier champ. - À FAIRE : placez
autofocussur l'action sécurisée (comme "Annuler" ou "Conserver") dans une confirmation destructive. - À ÉVITER : Écrire du code pour renvoyer le curseur sur l'élément déclencheur lors de la fermeture. Le navigateur la renvoie à l'appelant.
Opening
- À FAIRE : Ouvrez de manière déclarative avec
command="show-modal"etcommandforsur un<button>. Consultez Contrôle déclaratif des boîtes de dialogue et des popovers. - À FAIRE : Utilisez
dialog.showModal()lors de l'ouverture à partir d'un script.
Closing
- À FAIRE : Fermez avec les boutons
<form method="dialog">oucommand="close". - À FAIRE : Choisissez
closedbydélibérément :any(fermeture par clic en dehors de la boîte de dialogue etEsc),closerequest(Escuniquement, valeur par défaut de la boîte de dialogue modale) ounone(fermeture contrôlée par le développeur uniquement). - À FAIRE : Utilisez
command="request-close"ourequestClose()pour les boutons "Annuler" qui doivent respecter la logique des modifications non enregistrées. Ils déclenchent d'abordcancelet ignorentclosedby. - À FAIRE : Traitez
closedbycomme une amélioration progressive.Escet le geste de retour sur mobile ferment déjà les fenêtres modales sans cela.
Hooking into close
- À FAIRE : lisez
dialog.returnValuedans un écouteurclosepour savoir quel bouton a été utilisé, puis réinitialisez-le avecdialog.returnValue = ''.Escet d'autres requêtes de fermeture ne définissent aucune valeur, et les navigateurs ne s'accordent pas sur la suppression de la valeur précédente. Par conséquent, sans la réinitialisation, une valeur obsolète peut être divulguée. Traitez''comme "fermé sans choix". - À FAIRE : Utilisez
cancelpour intercepterEscetrequest-close, mais ne vous fiez pas àpreventDefault()pour les appuis répétés surEscsans activation de l'utilisateur. - À FAIRE : Utilisez
closedby="none"avec une étape de confirmation explicite pour les formulaires modifiés. - À FAIRE : indiquez à l'utilisateur pourquoi la boîte de dialogue est restée ouverte lorsque
cancelest empêché. Affichez un message visible et référencez-le avecaria-describedbyà partir de la commande qui reçoit la sélection (par exemple, "Supprimer"), afin qu'un lecteur d'écran le lise lorsque la sélection se déplace. Sans cela, lorsque le curseur se déplace vers un nouveau bouton, seul le nom de ce bouton est annoncé. - À ÉVITER : Ne laissez pas la confirmation déplacer les commandes sous le pointeur. Réservez l'espace du message (masquez-le avec
visibility, et nondisplayouhidden) et ajoutez le bouton de destruction là où les boutons existants ne bougent pas (par exemple, en premier dans une ligne alignée à droite), afin qu'un deuxième clic au même endroit ne puisse pas annuler les modifications de l'utilisateur.
Boîtes de dialogue non modales
- À FAIRE : Utilisez
<dialog popover>(autooumanual) pour un panneau de couche supérieure etdialog.show()lorsque vous ne souhaitez pas de couche supérieure. - À FAIRE : Incluez un bouton de fermeture visible dans chaque boîte de dialogue non modale.
Arrière-plan et style
- À FAIRE : stylisez
::backdropet utilisezdialog:modalpour cibler les styles réservés aux modaux. - À FAIRE : Laissez l'animation sur Animer des éléments vers et depuis la couche supérieure, le glissement sur Créer un élément déplaçable et le reparentage sur UI de couche supérieure persistante.
- À ÉVITER : Définir
displaysurdialogsans condition. Il remplace l'état fermé. Définissez son champ d'application avecdialog[open].
Accessibilité
- À ÉVITER : n'ajoutez pas
role="dialog",aria-modal, un piège de sélection manuel ni l'activation/la désactivation deinertà une boîte de dialogue modale native. - À FAIRE : Utilisez un élément
<button>natif avec un nom accessible pour les commandes de fermeture. - À FAIRE : Utilisez un niveau de titre cohérent pour les titres des boîtes de dialogue.
Stratégies de remplacement
closedby (dialog-closedby)
Sans closedby, une boîte de dialogue modale se ferme toujours avec Esc et le geste de retour. Seul le clic en dehors est perdu, il s'agit donc d'une dégradation élégante. closedby="none" est ignoré de la même manière : Esc déclenche cancel, puis ferme la boîte de dialogue. Conservez la protection contre les modifications non enregistrées dans un gestionnaire cancel, et pas seulement dans closedby="none", afin qu'elle protège également les cas où closedby est manquant. Si votre cible de référence nécessite une fermeture par simple pression, ajoutez ceci là où closedby est manquant :
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
Traitez le confinement du défilement comme une amélioration progressive. Ne détectez pas la fonctionnalité : @supports indique que la propriété est compatible, même si les conteneurs non défilables l'ignorent. Lorsqu'il n'est pas appliqué, la boîte de dialogue contient toujours un défilement chaque fois que son propre contenu déborde, et showModal() rend toujours la page inerte. Ne pas utiliser overflow: hidden ou position: fixed sur html ou body, ce qui supprime la barre de défilement racine et décale la mise en page.
Commandes de l'invocateur (command/commandfor)
Si votre cible de référence n'inclut pas les commandes d'appel, conservez le même balisage et chargez le polyfill de manière conditionnelle (la même expérience, en tant qu'amélioration progressive). Détectez l'interface et ne la chargez jamais de manière inconditionnelle :
// 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
}
Seules les règles 2 et 3 utilisent Popover.
Solutions de remplacement et compatibilité des navigateurs pour Popover
L'API Popover est principalement une amélioration progressive, mais ses comportements déterminants (promotion de la couche supérieure, fermeture par clic en dehors de l'élément et appel de popovertarget) n'ont pas d'équivalent CSS uniquement. Les anciens navigateurs ont besoin d'un polyfill ou d'un remplacement manuel si vous préférez ne pas en fournir.
Polyfill : pour que l'attribut popover soit compatible avec les anciens navigateurs, chargez @oddbird/popover-polyfill de manière conditionnelle. OBLIGATOIRE : Détectez la fonctionnalité en vérifiant la propriété popover sur HTMLElement.prototype, et chargez le polyfill uniquement lorsque la compatibilité native est manquante. Ne le chargez PAS de manière inconditionnelle.
Avec un bundler ou une carte d'importation :
// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
import("@oddbird/popover-polyfill");
}
Sans bundler, importez depuis un CDN dans 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>
Mise en garde concernant l'application de styles : le polyfill ne peut pas définir la véritable pseudo-classe :popover-open. Il applique donc une classe .\:popover-open à la place. OBLIGATOIRE : Combinez les deux avec :is() ou :where(), sinon les navigateurs qui ne disposent pas de :popover-open ignorent l'intégralité de la règle :
[popover]:is(:popover-open, .\:popover-open) {
display: block;
}
Vous pouvez également utiliser position: fixed pour une solution de remplacement sans polyfill et calculer manuellement les coordonnées via getBoundingClientRect(), ou vous appuyer sur le positionnement par défaut avec inset: auto si cela convient à votre cas d'utilisation.