Introduzione
Quando manipoliamo il DOM con JavaScript, non dobbiamo aggiornare soltanto ciò che l’utente vede. Un’interfaccia moderna deve comunicare correttamente il proprio stato anche alle tecnologie assistive, come gli screen reader. Per questo motivo è importante sincronizzare il contenuto visibile con gli attributi HTML e ARIA.
ARIA, acronimo di Accessible Rich Internet Applications, mette a disposizione attributi come aria-expanded, aria-hidden, aria-label e aria-live. Questi attributi descrivono il ruolo e lo stato di un elemento, migliorando l’esperienza delle persone che utilizzano strumenti di assistenza.
In questo tutorial realizzeremo un pannello FAQ. Al clic su una domanda, la risposta verrà mostrata o nascosta. JavaScript aggiornerà contemporaneamente:
- il contenuto e la visibilità della risposta;
- l’attributo aria-expanded del pulsante;
- l’attributo aria-hidden della risposta;
- l’etichetta accessibile del pulsante.
L’obiettivo non è solo manipolare il DOM, ma mantenere coerenti struttura, stato visivo e informazioni accessibili.
Codice completo
Il seguente esempio può essere inserito in una pagina HTML che carica lo script dopo il markup, oppure eseguito in un ambiente online per JavaScript.
<section class="faq" aria-labelledby="faq-title">
<h2 id="faq-title">Domande frequenti</h2>
<article class="faq-item">
<h3>
<button
type="button"
class="faq-question"
aria-expanded="false"
aria-controls="answer-1">
Che cos´è il DOM?
</button>
</h3>
<div
id="answer-1"
class="faq-answer"
aria-hidden="true"
hidden>
Il DOM è la rappresentazione ad albero del documento HTML
che JavaScript può leggere e modificare.
</div>
</article>
<article class="faq-item">
<h3>
<button
type="button"
class="faq-question"
aria-expanded="false"
aria-controls="answer-2">
Che cosa fa JavaScript nel browser?
</button>
</h3>
<div
id="answer-2"
class="faq-answer"
aria-hidden="true"
hidden>
JavaScript può reagire alle interazioni e aggiornare
dinamicamente il contenuto della pagina.
</div>
</article>
</section>
<script>
const faq = document.querySelector(´.faq´);
faq.addEventListener(´click´, (event) => {
const button = event.target.closest(´.faq-question´);
// Ignora i clic che non provengono da una domanda.
if (!button) {
return;
}
const answerId = button.getAttribute(´aria-controls´);
const answer = document.getElementById(answerId);
if (!answer) {
return;
}
const isOpen = button.getAttribute(´aria-expanded´) === ´true´;
const nextState = !isOpen;
// Aggiorna lo stato accessibile del pulsante.
button.setAttribute(´aria-expanded´, String(nextState));
// Aggiorna la visibilità reale della risposta.
answer.hidden = !nextState;
// Comunica lo stesso stato alle tecnologie assistive.
answer.setAttribute(´aria-hidden´, String(!nextState));
// Aggiorna anche l´etichetta letta dallo screen reader.
button.setAttribute(
´aria-label´,
nextState ? ´Nascondi risposta´ : ´Mostra risposta´
);
});
</script> Spiegazione
Selezionare il contenitore principale
Il codice seleziona l’elemento section con document.querySelector():
const faq = document.querySelector(´.faq´); Invece di aggiungere un listener a ogni singolo pulsante, viene utilizzato un solo listener sul contenitore. Questa tecnica è chiamata delegazione degli eventi. È utile quando gli elementi appartengono allo stesso gruppo e riduce il numero di gestori registrati.
Individuare il pulsante cliccato
L’oggetto event.target rappresenta l’elemento effettivamente cliccato. Il metodo closest() cerca il primo antenato, incluso l’elemento stesso, che corrisponde al selettore indicato:
const button = event.target.closest(´.faq-question´); Questo approccio funziona anche se in futuro inseriamo un’icona o uno span all’interno del pulsante. Il codice continuerà a risalire fino al pulsante corretto.
Collegare pulsante e risposta
L’attributo aria-controls contiene l’identificativo dell’elemento controllato. Per esempio, il primo pulsante contiene aria-controls="answer-1". Possiamo quindi recuperare la risposta senza dipendere dalla posizione degli elementi nel DOM:
const answerId = button.getAttribute(´aria-controls´);
const answer = document.getElementById(answerId); Questa relazione esplicita rende il markup più comprensibile sia agli sviluppatori sia agli strumenti assistivi.
Leggere e aggiornare uno stato booleano
L’attributo aria-expanded contiene una stringa, non un valore booleano. Per controllare se il pannello è aperto, confrontiamo il valore con la stringa "true":
const isOpen = button.getAttribute(´aria-expanded´) === ´true´;
const nextState = !isOpen; Successivamente convertiamo il valore booleano in stringa usando String(). Questa conversione è esplicita e impedisce comportamenti ambigui:
button.setAttribute(´aria-expanded´, String(nextState)); Gestire la visibilità nel DOM
La proprietà hidden controlla la visibilità semantica dell’elemento. Quando vale true, il browser nasconde la risposta e questa non viene normalmente presentata allo screen reader:
answer.hidden = !nextState; In parallelo aggiorniamo aria-hidden, così lo stato è esplicito anche nel modello di accessibilità:
answer.setAttribute(´aria-hidden´, String(!nextState)); È importante non usare solamente una modifica grafica, come una proprietà CSS. Nascondere un elemento con il solo stile potrebbe lasciare informazioni incoerenti alle tecnologie assistive.
Best practice
- Preferisci elementi semantici: usa un vero elemento button per un’azione, invece di un div cliccabile.
- Non aggiungere ARIA inutilmente: gli elementi HTML nativi possiedono già comportamenti accessibili. ARIA deve completare il markup, non sostituirlo senza motivo.
- Mantieni gli stati sincronizzati: se una risposta è visibile, aggiorna sia la proprietà hidden sia gli attributi ARIA collegati.
- Usa identificativi univoci: ogni valore di aria-controls deve puntare a un solo elemento realmente presente nel DOM.
- Controlla i riferimenti: verifica che l’elemento selezionato esista prima di modificarlo. Questo evita errori quando il markup cambia.
- Considera la tastiera: un elemento button supporta già l’attivazione con Invio e Barra spaziatrice, mentre un elemento generico richiederebbe gestione aggiuntiva.
- Non usare solo il colore: lo stato aperto o chiuso dovrebbe essere riconoscibile anche attraverso testo, struttura o attributi accessibili.
Riepilogo
La gestione del DOM non consiste soltanto nel mostrare o nascondere elementi. Un aggiornamento corretto deve mantenere coerenti il contenuto HTML, lo stato visivo e il modello di accessibilità.
Nel nostro esempio abbiamo selezionato un contenitore, intercettato i clic con la delegazione degli eventi, recuperato l’elemento associato tramite aria-controls e aggiornato gli attributi aria-expanded e aria-hidden. Abbiamo inoltre usato la proprietà hidden, preferibile a una semplice manipolazione grafica quando l’elemento deve essere realmente escluso dalla pagina.
Approfondisci con risorse ufficiali
- MDN Web Docs: documentazione su Document.querySelector(), Element.closest() e Element.setAttribute().
- MDN Web Docs: guida alla proprietà HTML hidden.
- WAI-ARIA Authoring Practices: pattern ufficiali per accordion, dialoghi, tab e componenti interattivi accessibili.
- W3C WAI: principi e linee guida per progettare interfacce web accessibili.
