Bonjour,
Comme indiqué dans les précédentes réponses, la balise <dialog> associée à la méthode .showModal() gère nativement le piège de focus et l'overlay. Voici un exemple complet et fonctionnel qui répond à ton besoin.
Code HTML + CSS + JavaScript
code html/JS
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>ACME</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.9.0/dist/css/foundation.min.css" crossorigin="anonymous">
<style>
.required-label>span::after {
content: ' *';
color: red;
font-weight: bold;
}
</style>
</head>
<body>
<main>
<h1>Contactez-nous</h1>
<button class="button" aria-controls="form-contact" aria-expanded="false">Ouvrir le formulaire de contact</button>
<dialog id="form-contact" aria-labelledby="modal-title" aria-modal="true">
<form method="post">
<h2 id="modal-title">Formulaire de contact</h2>
<p>Les champs marqués d'un astérisque (*) sont obligatoires.</p>
<ul class="no-bullet">
<li><label for="contact-firstname"><span>Prénom</span><input type="text" name="firstname" id="contact-firstname" autocomplete="given-name"></label></li>
<li><label for="contact-lastname" class="required-label"><span>Nom</span><input type="text" name="lastname" id="contact-lastname" autocomplete="family-name" required></label></li>
<li><label for="contact-email" class="required-label"><span>Email</span><input type="email" name="email" id="contact-email" autocomplete="email" required></label></li>
<li><label for="contact-subject" class="required-label"><span>Sujet</span><input type="text" name="subject" id="contact-subject" required></label></li>
<li><label for="contact-message" class="required-label"><span>Message</span><textarea name="message" id="contact-message" required></textarea></label></li>
</ul>
<button class="button" type="submit">Envoyer</button>
<button type="button" class="button button--close">Fermer</button>
</form>
</dialog>
<script>
const trigger = document.querySelector('[aria-controls="form-contact"]')
const dialog = document.getElementById('form-contact')
const closeButton = dialog.querySelector('.button--close')
const firstInput = dialog.querySelector('input:first-of-type')
// Ouverture
trigger.addEventListener('click', () => {
dialog.showModal()
trigger.setAttribute('aria-expanded', 'true')
firstInput.focus()
})
// Fermeture
closeButton.addEventListener('click', () => {
dialog.close()
trigger.setAttribute('aria-expanded', 'false')
trigger.focus()
})
// Fermeture avec Escape
dialog.addEventListener('close', () => {
trigger.setAttribute('aria-expanded', 'false')
trigger.focus()
})
</script>
</main>
</body>
</html>
Points importants
1. aria-modal="true" indique aux technologies d'assistance que le contenu hors de la modale est inerte.
2. La méthode .showModal() gère nativement le piège de focus et l'overlay (::backdrop), sans besoin de JavaScript supplémentaire.
3. Le bouton "Fermer" utilise type="button" pour éviter toute soumission du formulaire, avec un simple dialog.close() en JavaScript.
4. Gestion de la touche Escape : la fermeture est automatique avec .showModal(), on écoute l'événement close pour gérer le retour de focus vers le déclencheur.
5. aria-expanded sur le déclencheur informe l'utilisateur de l'état de la modale (ouverte/fermée).
6. Labels explicites avec for/id pour une compatibilité optimale avec les lecteurs d'écran.
7. autocomplete sur les champs pertinents (given-name, family-name, email) pour faciliter la saisie (critère WCAG 1.3.5).
8. Champs obligatoires signalés par un astérisque en CSS via ::after, sans impacter la vocalisation par les technologies d'assistance.
Support navigateurs
La balise <dialog> est aujourd'hui supportée par tous les navigateurs modernes (Chrome, Firefox, Safari, Edge). Pour de la compatibilité avec d'anciens navigateurs, il existe des polyfills.
N'hésite pas si tu as des questions !
Modifié par Niuxe (04 Jun 2026 - 11:05)