Accessibilité
#Lien direct vers cette section
Pris en charge automatiquement
#Lien direct vers cette section
- Élément
<dialog> natif avec role="dialog", aria-labelledby, aria-describedby et aria-modal.
- Quand
without-header est actif, aria-label (basé sur label) remplace aria-labelledby — le nom accessible reste garanti même sans header visible.
- Le focus est déplacé à l’ouverture sur l’élément portant
autofocus, ou à défaut sur le premier élément focalisable, ou sur le bouton de fermeture si aucun n’est présent, ou sur le <dialog> lui-même si le header est absent — conforme
WCAG 2.1.2 ↗ (ouvre dans un nouvel onglet)No Keyboard Trap : le focus clavier peut toujours quitter un composant via les touches standard (Escape, Tab).
- Le focus est restauré sur l’élément déclencheur à la fermeture.
- Le scroll du
<body> est bloqué à l’ouverture et restauré à la fermeture, y compris avec plusieurs dialogs empilés.
- La touche Escape passe par
ar-dialog-hide : la fermeture peut donc être interceptée avec event.preventDefault() — comportement prévisible conforme
WCAG 3.2.2 ↗ (ouvre dans un nouvel onglet)On Input : modifier un composant UI ne doit pas provoquer de changement de contexte inattendu sans en avertir l'utilisateur au préalable.
- Quand une fermeture est bloquée, le composant secoue visuellement le dialog et annonce automatiquement
prevented-message via la zone d’annonce globale de la librairie.
- Les animations sont désactivées si
prefers-reduced-motion: reduce est actif.
À votre charge
#Lien direct vers cette section
- Fournir un
label descriptif. Un fallback Dialogue existe, mais il doit rester un filet de sécurité, pas le libellé final du composant. Un avertissement est émis dans la console si ni label ni slot="label" ne sont fournis.
without-header retire le bouton de fermeture. label devient requis (le slot label HTML est sans effet dans ce mode), et un moyen de fermeture doit rester accessible — close-on-backdrop, ou une action data-ar-dismiss/data-ar-accept dans votre contenu. Un avertissement est émis en développement si aucun n’est détecté (Échap reste toujours disponible).
- Toujours proposer une issue claire. Le backdrop est statique par défaut : prévoir un bouton de fermeture visible ou une action
data-ar-dismiss.
- Si vous bloquez la fermeture, renseigner
prevented-message. Le message doit expliquer quoi faire pour continuer ou confirmer l’action.
- Piloter le focus initial si nécessaire. Posez
autofocus sur l’élément qui doit recevoir le focus à l’ouverture — utile par exemple pour un champ de saisie dans un drawer.
Utilisation
#Lien direct vers cette section
Déclaration via data-ar-dialog-open
#Lien direct vers cette section
Plutôt que de piloter open en JavaScript, un élément cliquable peut ouvrir un dialog par son id via l’attribut data-ar-dialog-open — un écouteur global au niveau du document intercepte le clic :
<button data-ar-dialog-open="confirm-dialog">Supprimer</button>
<ar-dialog id="confirm-dialog" label="Confirmer la suppression">
Cette action est irréversible.
<div slot="footer">
<button data-ar-dismiss>Annuler</button>
<button data-ar-accept>Confirmer</button>
</div>
</ar-dialog>
Lien direct vers cette section
Le conteneur du slot header-actions expose son propre ::part(header-actions), indépendant du layout titre/fermeture — utile pour définir un espacement entre plusieurs actions sans affecter le reste du header :
ar-dialog::part(header-actions) {
gap: 0.5rem;
}
Pour masquer visuellement le titre sans casser le nom accessible du dialog, masquez ::part(title) avec la technique sr-only standard — pas display: none ni visibility: hidden, qui peuvent faire perdre le nom accessible calculé via aria-labelledby dans certains navigateurs :
ar-dialog::part(title) {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
}
Cette technique sort le titre du flux (position: absolute), ce qui rompt le margin-inline-end: auto utilisé par le composant pour aligner header-actions et le bouton de fermeture en fin de header. Complétez avec justify-content sur ::part(header) pour conserver cet alignement :
ar-dialog::part(header) {
justify-content: flex-end;
}
Le slot n’est pas limité à des boutons isolés — un seul enfant slotté peut lui-même porter son
propre layout interne (actions à gauche, pagination à droite, titre sr-only…). Voir la variante
« Header avancé » pour une démonstration complète.
Pour retirer entièrement le header, voir la variante « Sans header ».
Cycle d’événements
#Lien direct vers cette section
Tous les événements portent detail: { id } (l’id du dialog). data-ar-dismiss et data-ar-accept sont deux conventions distinctes de ar-dialog-hide : elles permettent de distinguer une annulation (“Annuler”) d’une confirmation (“Confirmer”) sur le même geste de fermeture. La liste complète des événements, avec leur annulabilité, est dans la Référence API.
document.getElementById('confirm-dialog').addEventListener('ar-dialog-accepted', (e) => {
if (e.defaultPrevented) return;
console.log('Confirmé, dialog :', e.detail.id);
});
Fermeture différée
#Lien direct vers cette section
Pour enregistrer avant de fermer, annulez ar-dialog-accepted et posez pending dans le même gestionnaire. Le dialog reste ouvert, annonce l’attente au lieu d’un blocage et ignore les nouvelles tentatives de fermeture. Une fois la réponse reçue, levez pending puis fermez, ou laissez le dialog ouvert en cas d’échec :
dialog.addEventListener('ar-dialog-accepted', async (e) => {
e.preventDefault();
dialog.pending = true; // avant le premier await
try {
await save();
dialog.open = false;
} finally {
dialog.pending = false;
}
});
Le dialog lit pending dès le retour du gestionnaire : posez-le directement sur l’élément, avant tout await. Un pending que votre code applicatif ne reflète sur l’élément qu’après coup (liaison d’attribut mise à jour au prochain rendu, par exemple) arrive trop tard : le dialog a déjà traité l’annulation comme un blocage, avec sa secousse et son annonce. Une liaison peut ensuite refléter l’état affiché, mais elle ne remplace pas cette écriture.