Creare una finestra di dialogo accessibile con HTML e ARIA

by Anastasia P.
SHARE
Creare una finestra di dialogo accessibile con HTML e ARIA
© Guida-HTML5.it

Introduzione

Una finestra di dialogo, spesso chiamata modal, mostra contenuti temporanei sopra la pagina principale. È utile per confermare un’azione, visualizzare dettagli o chiedere una scelta all’utente. Tuttavia, una dialog non è accessibile automaticamente: chi usa una tastiera o uno screen reader deve capire quando la finestra è comparsa, quale contenuto contiene e come chiuderla.

In questo tutorial costruiremo una finestra di dialogo accessibile usando HTML semantico, attributi ARIA e JavaScript. L’esempio permette di aprire una dialog, spostare il focus sul suo contenuto, impedire di interagire accidentalmente con la pagina sottostante e restituire il focus al pulsante originale dopo la chiusura.

Prima di usare ARIA è importante ricordare una regola fondamentale: preferire sempre gli elementi HTML nativi. Un pulsante reale, ad esempio, è già riconosciuto correttamente dalle tecnologie assistive. ARIA serve per descrivere comportamenti o relazioni che HTML da solo non esprime in modo sufficiente.

Codice completo

<!-- Pulsante che apre la finestra di dialogo -->
<button type="button" id="open-dialog">
  Elimina il documento
</button>

<!-- Contenuto principale della pagina -->
<main id="main-content">
  <h2>Documento selezionato</h2>
  <p>Relazione annuale 2026.pdf</p>
</main>

<!-- Finestra di dialogo inizialmente nascosta -->
<div
  id="delete-dialog"
  role="dialog"
  aria-modal="true"
  aria-labelledby="dialog-title"
  aria-describedby="dialog-description"
  hidden>

  <h2 id="dialog-title">Conferma eliminazione</h2>

  <p id="dialog-description">
    Stai per eliminare definitivamente il documento
    “Relazione annuale 2026.pdf”. Questa operazione non può essere annullata.
  </p>

  <button type="button" id="cancel-delete">
    Annulla
  </button>

  <button type="button" id="confirm-delete">
    Elimina documento
  </button>
</div>

<script>
  const openButton = document.querySelector(´#open-dialog´);
  const dialog = document.querySelector(´#delete-dialog´);
  const cancelButton = document.querySelector(´#cancel-delete´);
  const confirmButton = document.querySelector(´#confirm-delete´);

  // Elemento che aveva il focus prima dell´apertura
  let previousFocusedElement;

  // Elementi interattivi presenti nella dialog
  const getFocusableElements = () => dialog.querySelectorAll(
    ´button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])´
  );

  function openDialog() {
    previousFocusedElement = document.activeElement;

    dialog.hidden = false;

    // Il focus entra nella finestra di dialogo
    cancelButton.focus();
  }

  function closeDialog() {
    dialog.hidden = true;

    // Il focus torna al controllo che ha aperto la dialog
    previousFocusedElement.focus();
  }

  openButton.addEventListener(´click´, openDialog);
  cancelButton.addEventListener(´click´, closeDialog);

  confirmButton.addEventListener(´click´, () => {
    // Qui si potrebbe eseguire l´eliminazione tramite API
    closeDialog();
  });

  // Chiude la dialog con il tasto Escape
  dialog.addEventListener(´keydown´, (event) => {
    if (event.key === ´Escape´) {
      closeDialog();
      return;
    }

    // Gestione del focus circolare con il tasto Tab
    if (event.key === ´Tab´) {
      const focusableElements = getFocusableElements();
      const firstElement = focusableElements[0];
      const lastElement = focusableElements[focusableElements.length - 1];

      if (event.shiftKey && document.activeElement === firstElement) {
        event.preventDefault();
        lastElement.focus();
      } else if (!event.shiftKey && document.activeElement === lastElement) {
        event.preventDefault();
        firstElement.focus();
      }
    }
  });
</script>

Spiegazione

Il ruolo dialog

L’attributo role="dialog" comunica agli screen reader che il contenuto rappresenta una finestra separata dal flusso principale della pagina. Non basta però aggiungere il ruolo: la dialog deve avere un nome accessibile.

In questo esempio, aria-labelledby="dialog-title" collega la finestra all’intestazione con identificativo dialog-title. Il testo dell’intestazione diventa quindi il nome annunciato all’utente. L’attributo aria-describedby collega invece la descrizione della situazione, permettendo allo screen reader di leggere anche le informazioni aggiuntive.

Il significato di aria-modal

aria-modal="true" indica che, mentre la dialog è aperta, l’utente dovrebbe interagire soltanto con essa. Questo attributo non blocca automaticamente la tastiera e non nasconde realmente la pagina sottostante: è necessario implementare questi comportamenti con JavaScript e, quando opportuno, con ulteriori tecniche HTML.

Per una dialog realmente modale, il focus deve rimanere all’interno della finestra. Nel codice, la gestione del tasto Tab crea un ciclo: dal primo elemento si torna all’ultimo usando Shift + Tab, mentre dall’ultimo si ritorna al primo usando Tab.

Gestione del focus

La gestione del focus è uno degli aspetti più importanti. Quando la finestra si apre, il focus viene spostato sul pulsante “Annulla”, così l’utente può agire immediatamente senza dover attraversare elementi esterni. Quando la finestra si chiude, il focus ritorna al pulsante che l’ha aperta.

Il tasto Escape consente di annullare rapidamente l’operazione. È una convenzione comune e particolarmente utile per gli utenti da tastiera.

Best practice

  • Usa un vero elemento <button>: non sostituirlo con un <div> cliccabile. Il pulsante possiede già supporto per tastiera, focus e semantica.
  • Fornisci sempre un nome accessibile: collega un titolo con aria-labelledby oppure usa aria-label quando non è disponibile un titolo visibile.
  • Descrivi lo scopo della dialog: aria-describedby è utile quando l’utente deve comprendere conseguenze o istruzioni prima di scegliere.
  • Non usare ARIA per correggere HTML scorretto: ARIA non sostituisce la struttura semantica e non aggiunge automaticamente il comportamento JavaScript.
  • Gestisci sempre il focus: una finestra che appare senza spostare il focus può risultare invisibile agli utenti di screen reader.
  • Permetti la chiusura con Escape: non obbligare l’utente a trovare un solo pulsante specifico per uscire.
  • Evita dialog troppo lunghe: per contenuti estesi è spesso preferibile una pagina dedicata, più semplice da navigare e condividere.
  • Verifica il contrasto e la visibilità: ARIA non risolve problemi di colore, dimensioni del testo o indicatori di focus poco visibili.
  • Testa con strumenti reali: prova la dialog con tastiera, zoom del browser, NVDA, VoiceOver o altri screen reader.

Riepilogo

Una finestra di dialogo accessibile richiede più del semplice aspetto grafico. Il ruolo dialog, gli attributi aria-modal, aria-labelledby e aria-describedby descrivono la struttura e le relazioni tra gli elementi. JavaScript deve occuparsi del comportamento: apertura, chiusura, focus iniziale, ritorno del focus, tasto Escape e contenimento della navigazione con Tab.

La soluzione migliore combina sempre HTML semantico, comportamento prevedibile e test pratici. ARIA deve migliorare l’esperienza, non diventare un sostituto degli elementi nativi o di una corretta progettazione dell’interfaccia.

Approfondisci con risorse ufficiali

  • WAI-ARIA Authoring Practices: linee guida ufficiali per progettare pattern accessibili, inclusa la dialog modale.
  • MDN Web Docs: documentazione su role="dialog", gestione del focus e attributi ARIA.
  • WCAG 2.2: criteri internazionali per accessibilità, focus visibile, navigazione da tastiera e comprensibilità.
  • WebAIM: guide pratiche per testare interfacce con tastiera e screen reader.

SHARE