In breve: il “dialogo” nel DOM è un componente UI che richiede attenzione a accessibilità, controllo del focus e interazione da tastiera. Oggi possiamo usare l’elemento nativo <dialog>
, oppure implementare modali custom con ARIA. In questa guida vediamo quando usarlo, come farlo bene e cosa evitare, con esempi step-by-step.
Cos’è un dialogo nel DOM e quando usarlo
Un dialogo (o modale) è un pannello che si sovrappone alla pagina per richiedere un’azione o fornire informazioni contestuali. Va usato con parsimonia: è ideale per conferme, form brevi, scelte critiche o interruzioni intenzionali.
Non è adatto per contenuti lunghi o navigazione complessa (in quel caso, meglio una pagina dedicata).
Opzione 1: usare l’elemento nativo <dialog>
<dialog>
è un elemento HTML che fornisce un comportamento nativo per finestre di dialogo. Offre i metodi show()
, showModal()
e close()
, oltre all’attributo open
e all’evento close
.
Esempio base con <dialog>
<button id="openDialog">Apri dialog</button>
<dialog id="myDialog">
<form method="dialog">
<h2>Conferma azione</h2>
<p>Vuoi davvero procedere?</p>
<menu>
<button value="cancel">Annulla</button>
<button value="ok">Conferma</button>
</menu>
</form>
</dialog>
<script>
const dlg = document.getElementById('myDialog');
document.getElementById('openDialog').addEventListener('click', () => {
dlg.showModal(); // show() non è modale; showModal() sì
});
dlg.addEventListener('close', () => {
console.log('Dialog chiuso con valore:', dlg.returnValue);
});
</script>
Con method="dialog"
il pulsante invia il valore a returnValue
e chiude il dialogo.
Vantaggi: API semplici, focus e scroll lock gestiti in gran parte dal browser.
Attenzione: verifica la compatibilità dei browser target; per ambienti legacy valuta un polyfill.
Stilizzare un <dialog>
dialog::backdrop {
background: rgba(0,0,0,.45);
}
dialog {
border: 0;
border-radius: 12px;
padding: 20px;
max-width: 520px;
width: 90%;
box-shadow: 0 20px 60px rgba(0,0,0,.2);
}
Il ::backdrop
controlla lo sfondo scurito. Ricorda contrasto sufficiente e dimensioni responsive.
Opzione 2: modale custom con ARIA (quando non puoi usare <dialog>)
Se non puoi usare <dialog>
, crea una modale con markup semantico e ruoli ARIA.
Gli ingredienti chiave sono:
role="dialog"
orole="alertdialog"
(se richiede attenzione immediata).aria-modal="true"
per indicare che il contenuto sottostante è inerte.aria-labelledby
e/oaria-describedby
per testo e descrizione.- Focus trap e gestione Esc / Tab via JavaScript.
Markup ARIA consigliato
<button id="open">Apri impostazioni</button>
<div class="backdrop" hidden></div>
<div class="modal" role="dialog" aria-modal="true"
aria-labelledby="modal-title" aria-describedby="modal-desc" hidden>
<h2 id="modal-title">Impostazioni account</h2>
<p id="modal-desc">Aggiorna email e preferenze.</p>
<form>
<label>Email<br><input type="email"></label>
<div class="actions">
<button type="button" id="cancel">Annulla</button>
<button type="submit">Salva</button>
</div>
</form>
</div>
Focus trap e tastiera
const openBtn = document.getElementById('open');
const modal = document.querySelector('.modal');
const backdrop = document.querySelector('.backdrop');
let lastFocused = null;
function openModal() {
lastFocused = document.activeElement;
backdrop.hidden = false;
modal.hidden = false;
const focusables = modal.querySelectorAll('a,button,input,select,textarea,[tabindex]:not([tabindex="-1"])');
(focusables[0] || modal).focus();
function onKeydown(e){
if (e.key === 'Escape'){ closeModal(); }
if (e.key === 'Tab'){
const list = Array.from(focusables);
const i = list.indexOf(document.activeElement);
if (e.shiftKey && (i === 0 || i === -1)) { e.preventDefault(); list[list.length - 1].focus(); }
else if (!e.shiftKey && i === list.length - 1){ e.preventDefault(); list[0].focus(); }
}
}
modal.addEventListener('keydown', onKeydown);
modal._cleanup = () => modal.removeEventListener('keydown', onKeydown);
}
function closeModal(){
backdrop.hidden = true;
modal.hidden = true;
modal._cleanup && modal._cleanup();
lastFocused && lastFocused.focus();
}
openBtn.addEventListener('click', openModal);
document.getElementById('cancel').addEventListener('click', closeModal);
backdrop.addEventListener('click', closeModal);
Questa logica impedisce di “uscire” dal dialogo con Tab, chiude con Esc, e ripristina il focus al punto di origine. È un requisito di accessibilità fondamentale.
Accessibilità: checklist essenziale
- Attivazione chiara: il pulsante/link che apre la modale deve essere evidente e avere un testo descrittivo.
- Ruoli e nomi accessibili: usa
role
,aria-modal
,aria-labelledby
/aria-describedby
quando necessario. - Focus management: sposta il focus dentro il dialogo all’apertura, mantienilo intrappolato, restituiscilo all’elemento che ha aperto la modale alla chiusura.
- Tastiera: supporta Esc per chiudere, Tab/Shift+Tab per navigare.
- Contrasto: testo e pulsanti con contrasto AA almeno 4.5:1; backdrop non deve rendere il contenuto illeggibile.
- Scroll del body: bloccalo quando la modale è aperta per evitare “salti” (con
overflow:hidden
sul body o conshowModal()
).
Animazioni e micro-interazioni
Le animazioni migliorano la percezione: usa fade/scale brevi (150–250ms). Evita rimbalzi eccessivi. Rispetta la preferenza utente prefers-reduced-motion
.
.backdrop{position:fixed;inset:0;background:rgba(0,0,0,.46);opacity:0;transition:.2s}
.modal{position:fixed;inset:auto;left:50%;top:50%;transform:translate(-50%,-50%) scale(.98);
background:#fff;border-radius:12px;min-width:280px;max-width:560px;opacity:0;transition:.2s}
.backdrop[hidden], .modal[hidden]{display:none}
.backdrop.show{opacity:1}
.modal.show{opacity:1;transform:translate(-50%,-50%) scale(1)}
@media (prefers-reduced-motion: reduce){
.backdrop,.modal{transition:none}
}
SEO e performance: cosa conta davvero
- Indicizzabilità: il contenuto della modale di solito non è critico per l’indicizzazione. Evita di nascondere informazioni chiave solo dietro modali.
- Link interni: se la modale replica contenuti, offri anche una pagina dedicata con URL indicizzabile.
- Core Web Vitals: carica JS/CSS della modale in modo leggero e differito; evita layout shift aprendo la modale con overlay fixed.
- Accessibilità = SEO: ARIA corretta, focus e tastiera migliorano UX e spesso correlano con segnali comportamentali migliori.
Pattern comuni e anti-pattern
Buoni pattern
- Conferma azioni distruttive (“Eliminare definitivamente?”).
- Login/Signup rapidi quando l’azione è contestuale.
- Anteprime rapide (immagini/video) con tasti freccia per navigare.
Da evitare
- Modali a catena (una modale che apre un’altra modale).
- Modali su mobile per contenuti lunghi (meglio pagine a schermo intero).
- Blocco della pagina senza via d’uscita o senza pulsante evidente per chiudere.
Confronto rapido: <dialog> vs ARIA custom
Caratteristica | <dialog> | Custom ARIA |
---|---|---|
API | Nativa (show/showModal/close) | JS personalizzato |
Focus/scroll | Gestione nativa decente | Da implementare manualmente |
Compatibilità | Buona, ma verifica target | Totale, ma più lavoro |
Tempo sviluppo | Minore | Maggiore |
Esempio completo: dialog di conferma con <dialog> + animazione CSS
<button id="danger">Elimina elemento</button>
<dialog id="confirm">
<h3>Sei sicuro?</h3>
<p>Questa azione non è reversibile.</p>
<form method="dialog">
<menu>
<button value="cancel">Annulla</button>
<button value="ok" class="danger">Elimina</button>
</menu>
</form>
</dialog>
<style>
dialog{opacity:0;transform:translateY(8px);transition:.18s}
dialog[open]{opacity:1;transform:translateY(0)}
dialog::backdrop{background:rgba(0,0,0,.45)}
.danger{background:#e11d48;color:#fff;border:0;padding:.6rem 1rem;border-radius:8px}
</style>
<script>
const d = document.getElementById('confirm');
document.getElementById('danger').addEventListener('click', () => d.showModal());
d.addEventListener('close', () => {
if (d.returnValue === 'ok') {
// esegui l’azione distruttiva
console.log('Elemento eliminato');
}
});
</script>
Errori comuni (e come risolverli)
- Dimenticare il focus: senza focus trap, gli screen reader e gli utenti da tastiera “perdono” il contesto. Implementa sempre la gestione di Tab.
- Assenza di etichetta: mancano
aria-labelledby
/aria-describedby
. Associa un titolo semanticamente corretto. - Chiusura non ovvia: inserisci un pulsante “Chiudi” chiaro, oltre a supportare Esc.
- Contenuti critici solo in modale: prevedi una pagina alternativa indicizzabile.
- Backdrop non cliccabile: se la UX lo consente, permetti di chiudere cliccando l’overlay (ma mantieni anche un pulsante dedicato).
Conclusioni
I dialoghi nel DOM richiedono cura: che tu scelga <dialog>
o una soluzione ARIA custom, il traguardo è una modale accessibile, chiara e performante. Parti dai pattern base qui sopra, adatta stile e interazioni al tuo progetto e testa sempre con tastiera e screen reader.