Créez des modales natives en HTML5 avec la balise <dialog> : syntaxe, attributs, pseudo-éléments ::backdrop, accessibilité et exemple complet. Sans dépendre de Bootstrap ou JavaScript.
Introduction : le natif avant Bootstrap
Pendant des années, les développeurs web ont dû choisir entre :
- jQuery UI ou Bootstrap Modal : puissants mais lourds
- Modales custom : flexibles mais chronophages (accessibilité, animations, gestion du clavier)
Aujourd'hui, la balise <dialog> (introduite en HTML5 et maintenant supportée par tous les navigateurs modernes) offre une troisième voie : native, légère et accessible par défaut.
Avantages de <dialog> natif
- Zéro dépendance : pas de Bootstrap, jQuery, ou libraries tiers
- Accessible par défaut : focus management, ARIA roles automatiques, lecteurs d'écran
- Légère : quelques bytes de HTML/CSS, pas de JavaScript obligatoire
- Performante : optimisée nativement par les navigateurs
- API simple : show(), showModal(), close()
- Stylisable : ::backdrop, transitions CSS, propriétés custom
Bien sûr, Bootstrap modales restent utiles pour les projets complexes avec designs élaborés. Mais pour 90% des cas d'usage simples à intermédiaires, <dialog> est la solution idéale.
Syntaxe et structure basique
La balise <dialog> minimale
Voici la structure la plus simple :
<!-- Définir la dialog -->
<dialog id="monDialog">
<h2>Titre de ma modale</h2>
<p>Contenu de la modale.</p>
<button onclick="document.getElementById('monDialog').close()">
Fermer
</button>
</dialog>
<!-- Bouton pour ouvrir -->
<button onclick="document.getElementById('monDialog').showModal()">
Ouvrir la modale
</button>
C'est tout ce qu'il faut ! Aucun CSS spécial, aucune dépendance. La modale s'affiche au centre de l'écran avec un overlay semi-transparent.
Contenu complexe
Les dialogs peuvent contenir n'importe quel HTML :
<dialog id="confirmDialog">
<!-- Header -->
<header>
<h2>Confirmez votre action</h2>
<!-- Bouton native X -->
<button aria-label="Fermer" onclick="this.closest('dialog').close()">
✕
</button>
</header>
<!-- Main content -->
<main>
<p>Êtes-vous sûr de vouloir supprimer cet élément ?</p>
<p><strong>Cette action est irréversible.</strong></p>
</main>
<!-- Footer avec actions -->
<footer class="d-flex gap-2 justify-content-end">
<button onclick="this.closest('dialog').close('cancel')"
class="btn btn-secondary">
Annuler
</button>
<button onclick="this.closest('dialog').close('confirm')"
class="btn btn-danger">
Supprimer
</button>
</footer>
</dialog>
Notez comment on utilise les méthodes : showModal() pour ouvrir et close() pour fermer, avec un argument optionnel pour retourner une valeur.
Attributs : open et comportement modal
L'attribut open
Cet attribut booléen indique si la dialog est affichée :
<!-- Dialog visible au chargement (non-modale) -->
<dialog open>
<p>Je suis visible par défaut en mode non-modal.</p>
</dialog>
Attention : open seul place la dialog en mode non-modal (pas de backdrop bloquant). La page reste interactive derrière.
Modale vs non-modale
Il y a deux façons d'afficher une dialog :
// 1) Mode NON-MODAL (non-bloquant)
dialog.show();
// → L'utilisateur peut cliquer derrière
// → Pas de backdrop grisé
// → Parfait pour des info-bulles, sidebars
// 2) Mode MODAL (bloquant)
dialog.showModal();
// → L'utilisateur ne peut pas cliquer derrière
// → Backdrop semi-transparent apparaît
// → L'interaction est limitée à la dialog
Utilisez showModal() pour les confirmations, formulaires importants. Utilisez show() pour les notifications non-critiques.
Pseudo-éléments ::backdrop et styling
Personnaliser l'overlay
Le pseudo-élément ::backdrop permet de styliser l'overlay sombre derrière la modale :
/* Style par défaut du backdrop */
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
/* Personnalisé : gradient, blur, etc. */
dialog::backdrop {
background: linear-gradient(135deg, rgba(0,0,0,0.3), rgba(0,0,0,0.8));
backdrop-filter: blur(2px); /* Pour les navigateurs supportant backdrop-filter */
}
Styliser la dialog elle-même
/* Style de base */
dialog {
border: none;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
max-width: 600px;
padding: 2rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* Mode sombre optionnel */
@media (prefers-color-scheme: dark) {
dialog {
background-color: #1e1e1e;
color: #e0e0e0;
}
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.8);
}
}
/* Animations d'entrée/sortie */
dialog {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
Bootstrap n'est pas nécessaire : utilisez flexbox, grid, et les utilitaires CSS natifs pour organiser le contenu.
Interactions JavaScript : show() et showModal()
Exemple complet avec gestion d'événements
// Récupérer la référence
const dialog = document.getElementById('myDialog');
const openBtn = document.getElementById('openBtn');
const closeBtn = document.getElementById('closeBtn');
// Ouvrir en mode modal
openBtn.addEventListener('click', () => {
dialog.showModal();
});
// Fermer
closeBtn.addEventListener('click', () => {
dialog.close();
});
// Écouter la fermeture (important pour actions post-modale)
dialog.addEventListener('close', () => {
console.log('Dialog fermée. Valeur retournée :', dialog.returnValue);
if (dialog.returnValue === 'confirm') {
// Utilisateur a cliqué "Confirmer"
}
});
// Fermer en cliquant en dehors (backdrop)
dialog.addEventListener('click', (e) => {
if (e.target === dialog) {
dialog.close('cancel');
}
});
Gestion du clavier
Les dialogs modales capturent automatiquement la touche Escape. Mais vous pouvez personnaliser :
dialog.addEventListener('cancel', (e) => {
// Utilisateur a appuyé sur Escape
console.log('Dialog annulée par Escape');
// Optionnel : e.preventDefault() pour empêcher la fermeture
});
// Gestion personnalisée d'autres touches
dialog.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
// Ctrl+Entrée = soumettre
dialog.close('confirm');
}
});
Formulaires dans <dialog>
Modale de formulaire complet
<dialog id="contactDialog">
<form method="dialog">
<!-- method="dialog" = ferme la dialog à la soumission -->
<h2>Nous contacter</h2>
<div class="mb-3">
<label for="name" class="form-label">Nom</label>
<input type="text" id="name" class="form-control" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
<input type="email" id="email" class="form-control" required>
</div>
<div class="mb-3">
<label for="message" class="form-label">Message</label>
<textarea id="message" class="form-control" rows="4" required></textarea>
</div>
<div class="d-flex gap-2 justify-content-end">
<button type="button" formmethod="dialog" value="cancel" class="btn btn-secondary">
Annuler
</button>
<button type="submit" value="confirm" class="btn btn-primary">
Envoyer
</button>
</div>
</form>
</dialog>
<script>
const contactDialog = document.getElementById('contactDialog');
const contactForm = contactDialog.querySelector('form');
// Récupérer le formulaire au submit
contactDialog.addEventListener('close', () => {
if (contactDialog.returnValue === 'confirm') {
const name = document.getElementById('name').value;
const email = document.getElementById('email').value;
const message = document.getElementById('message').value;
console.log('Envoi du formulaire :', { name, email, message });
// Appel API, etc.
// Réinitialiser après envoi
contactForm.reset();
}
});
</script>
Astuce : Utilisez method="dialog" sur le formulaire pour fermer automatiquement la dialog à la soumission.
Accessibilité et bonnes pratiques
Points clés d'accessibilité
Les dialogs natives gèrent beaucoup d'accessibilité automatiquement, mais quelques points à vérifier :
<dialog id="a11yDialog" role="dialog" aria-labelledby="dialogTitle">
<h2 id="dialogTitle">Titre accessible</h2>
<p id="dialogDesc">Description utile du dialogue.</p>
<!-- aria-describedby lie la description au titre -->
<!-- Les lecteurs d'écran annoncent l'intention de la dialog -->
<form>
<!-- Tous les contrôles doivent être accessibles au clavier -->
<label for="firstName">Prénom <abbr title="requis">*</abbr></label>
<input id="firstName" required>
<button type="submit">Envoyer</button>
<button type="button" onclick="this.closest('dialog').close()">
<span class="visually-hidden">Fermer la dialog</span>
✕
</button>
</form>
</dialog>
Bonnes pratiques
- Focus trap : La modale capture le focus ; l'utilisateur ne peut naviguer que dans la modale
- Escape key : Autorisez la fermeture avec la touche Escape
- Labels explicites : Utilisez aria-labelledby pour lier le titre
- Contraste : Vérifiez le contraste entre texte et fond (WCAG 4.5:1 minimum)
- Responsive : Adaptez la taille sur mobile avec media queries
/* Responsive : plein écran sur mobile */
@media (max-width: 768px) {
dialog {
max-width: 90vw;
max-height: 90vh;
margin: auto;
}
}
Conclusion et alternatives
<dialog> native offre une solution légère, accessible et performante pour les modales. C'est la première option à considérer dans la plupart des projets.
Quand utiliser <dialog> :
- Formulaires de contact, login, confirmations
- Modales d'alerte ou d'information
- Projets sans dépendances externes
- Applications mobiles légères
Quand utiliser Bootstrap ou autres libraries :
- Designs complexes avec multiples animations
- Intégration avec des composants Bootstrap existants
- Support IE11 (nécessite polyfill pour
<dialog>) - Carousel, accordions, ou transitions avancées dans la modale
Pour débuter, testez <dialog> sur vos prochains projets. Vous découvrirez qu'elle résout 90% des besoins en modales, avec zero dépendances.