Retour au cours

frontend / html

L'élément dialog et la Popover API

Leçon 201 exercice

Explication

Ce que vous allez apprendre

  • Ouvrir une modale accessible en un seul appel avec dialog.showModal()
  • Récupérer la réponse de l'utilisateur via <form method="dialog"> et dialog.returnValue
  • Styliser le fond assombri natif avec le pseudo-élément ::backdrop
  • Créer un menu ou tooltip flottant avec l'attribut popover, sans gérer de z-index
  • Choisir entre dialog.show() (non modal) et dialog.showModal() (modal, focus piégé)

Dans quel contexte ?

Un développeur reprend une ancienne modale de confirmation de suppression codée à la main sur gestion-utilisateurs.html : plus de 80 lignes de JavaScript pour piéger le focus, gérer Escape et afficher un fond assombri, avec un bug résiduel de focus qui s'échappe parfois vers l'arrière-plan. En migrant vers <dialog> natif et dialog.showModal(), tout ce comportement (piège du focus, backdrop, fermeture au clavier) est géré nativement par le navigateur, et le bug disparaît avec la suppression du code fait maison.

Étape 1 : le travail que demandait une modale avant

Avant l'arrivée de <dialog>, créer une modale accessible demandait un travail considérable, déjà entrevu dans la leçon sur le focus : piéger le Tab à l'intérieur, fermer avec Escape, restaurer le focus à la fermeture, gérer un fond semi-transparent.

Étape 2 : ce que <dialog> change

Toutes ces étapes, il fallait les coder et tester manuellement à chaque projet. <dialog> intègre nativement tout ce travail dans le navigateur lui-même.

Étape 3 : un seul appel qui fait tout

Concrètement, un seul appel JavaScript suffit : dialog.showModal() déclenche automatiquement le piège du focus, l'affichage d'un fond assombri, et la fermeture au clavier avec Escape.

Étape 4 : la leçon à retenir de cet exemple

C'est exactement le comportement qu'on codait à la main dans la leçon précédente sur l'accessibilité du focus, désormais offert gratuitement. Retenez le réflexe : chercher d'abord une solution HTML native avant de réinventer un comportement complexe en JavaScript.

Bonne pratique

Avant de coder une modale à la main ou d'installer une librairie tierce, vérifiez toujours si <dialog> (ou l'attribut popover pour un menu flottant) ne couvre pas déjà le besoin nativement — c'est souvent le cas, avec une accessibilité intégrée gratuitement.

Étape 5 : une astuce peu connue, le formulaire qui ferme tout seul

Il y a un détail élégant à connaître : <form method="dialog"> fait qu'une soumission ferme automatiquement le dialog SANS requête réseau, et transmet la value du bouton cliqué dans dialog.returnValue.

On peut ainsi savoir, après fermeture, si l'utilisateur a cliqué "Annuler" ou "Confirmer", sans code JavaScript supplémentaire.

Étape 6 : un problème différent, le z-index

Changeons de sujet pour finir. Les menus déroulants et tooltips posaient traditionnellement un problème récurrent : s'assurer qu'ils s'affichent bien AU-DESSUS de tout le reste, une bataille de z-index sans fin.

La solution, l'attribut popover

L'attribut popover résout ce problème d'un coup : l'élément est rendu dans le "top layer" du navigateur, une couche toujours au-dessus de tout, avec fermeture automatique au clic extérieur intégrée.

BesoinÉlément natifCe qu'il gère automatiquement
Modale de confirmation<dialog> + showModal()Focus trap, backdrop, fermeture Escape
Menu ou tooltip flottant<div popover>Top layer (au-dessus de tout), fermeture au clic extérieur
Panneau non bloquant<dialog> + show()Affichage sans piège de focus ni backdrop

Et pour la suite du parcours

Ce cours HTML touche ici à sa fin : vous disposez maintenant des bases solides pour structurer, rendre accessible et optimiser n'importe quelle page. La suite logique consiste à approfondir la mise en forme visuelle avec CSS.

Commandes & code

L'élément <dialog> et la Popover API

Modales et popovers natifs, accessibles par défaut, sans librairie JS.

html
<!-- dialog : modale native avec gestion de focus et Escape intégrée -->
<dialog id="modale-confirmation">
    <form method="dialog">
        <!-- method="dialog" : soumettre ferme le dialog sans requête réseau,
             et remplit returnValue avec la value du bouton cliqué -->
        <h2>Confirmer la suppression ?</h2>
        <p>Cette action est irréversible.</p>
        <button value="annuler">Annuler</button>
        <button value="confirmer">Supprimer</button>
    </form>
</dialog>

<button id="ouvrir-modale">Supprimer l'élément</button>
js
const dialog = document.getElementById("modale-confirmation");

document.getElementById("ouvrir-modale").addEventListener("click", () => {
    dialog.showModal();
    // showModal() : modale avec ::backdrop, focus piégé automatiquement,
    // Escape ferme automatiquement — tout ce qu'on codait à la main avant
});

dialog.addEventListener("close", () => {
    if (dialog.returnValue === "confirmer") {
        console.log("Suppression confirmée");
    }
    // returnValue vient du value du bouton qui a soumis le <form method="dialog">
});

// dialog.show() (sans "Modal") : ouvre en non-modal, sans backdrop,
// sans piéger le focus — rarement ce qu'on veut pour une confirmation
css
/* Le backdrop est un pseudo-élément natif, stylable */
dialog::backdrop {
    background: rgb(0 0 0 / 0.5);
    backdrop-filter: blur(2px);
}

dialog {
    border: none;
    border-radius: 12px;
    padding: 24px;
}

/* Animation d'entrée native (supportée récemment) */
dialog {
    transition: opacity 0.2s ease, display 0.2s allow-discrete;
    opacity: 0;
}
dialog[open] {
    opacity: 1;
}
html
<!-- Popover API : tooltips, menus, dropdowns natifs — sans dialog, sans z-index à gérer -->
<button popovertarget="menu-actions">Actions</button>

<div id="menu-actions" popover>
    <!-- popover : rendu dans le "top layer" navigateur, toujours au-dessus,
         fermé automatiquement au clic extérieur ou à Escape -->
    <button popovertarget="menu-actions" popovertargetaction="hide">Éditer</button>
    <button popovertarget="menu-actions" popovertargetaction="hide">Archiver</button>
</div>
js
// Contrôle en JS, équivalent aux attributs déclaratifs
const menu = document.getElementById("menu-actions");

menu.showPopover();
menu.hidePopover();
menu.togglePopover();

menu.addEventListener("toggle", (e) => {
    // e.newState : "open" ou "closed" — utile pour synchroniser un état d'UI
    console.log("Popover:", e.newState);
});
css
/* popover="manual" (au lieu de "auto") : ne se ferme pas tout seul,
   à gérer entièrement en JS — utile pour des notifications persistantes */
[popover] {
    inset: unset; /* désactive le centrage par défaut */
    top: anchor(bottom); /* CSS Anchor Positioning : ancre au bouton déclencheur */
    margin-top: 4px;
}

Résumé

  • showModal() gère seul le focus trap, le backdrop et la fermeture au Escape — ce qu'une modale en JS gérait à la main.
  • <form method="dialog"> ferme le dialog sans requête réseau et transmet la value du bouton dans returnValue.
  • L'attribut popover rend un élément dans le top layer natif, avec fermeture automatique au clic extérieur.
  • popovertargetaction="hide" permet un bouton interne au popover qui le referme, sans JavaScript.

Exercices pratiques

1 disponible
1

Mission : migrer la modale maison de gestion-utilisateurs.html

Objectif : Remplacer une modale de confirmation codée à la main, avec un bug de focus résiduel, par l'élément dialog natif.

Contexte

Un développeur reprend une ancienne modale de confirmation de suppression sur gestion-utilisateurs.html : plus de 80 lignes de JavaScript pour piéger le focus, gérer Escape et afficher un fond assombri, avec un bug résiduel de focus qui s'échappe parfois vers l'arrière-plan. La modale propose deux boutons : "Annuler" et "Supprimer".

Ta mission : migrer vers <dialog> natif.

Résoudre l’exercice →