Come realizzare un menù di navigazione con dropdown accessibile
Questa tipologia di guida è pensata e dedicata agli sviluppatori web che, come me, si trovano in certi casi a non sapere bene come progettare un certo componente, come i menù di navigazione o gli accordion, giusto per fare due esempi molto diffusi.
Tabella dei contenuti
Il menù di navigazione è probabilmente l’elemento più usato all’interno di un sito web. Dopotutto, permette all’utente di navigare all’interno del sito e raggiungere le pagine principali, o almeno quelle che si vuole mettere in evidenza.
Questi menù sono solitamente posizionati in alto (nell’header), a lato (aside) o in fondo (footer). Potrebbero essere presenti anche delle navigazioni contestuali, ad esempio inserite in una barra fissa che segue lo scorrimento della pagina.
Cosa sapere per l’accessibilità
In termini di accessibilità, le informazioni principali che dobbiamo conoscere sono le seguenti:
- Il tag contenitore dovrebbe essere un <nav>
- Se sono presenti più landmark di navigazione, ognuno deve avere una propria label, tramite uso di aria-label o aria-labelledby. Per landmark si intende quel tipo di elementi che hanno un ruolo specifico, quindi potrebbe essere un <nav> ma anche un <div role=”navigation”>
- In generale, la label può contenere solo il testo descrittivo omettendo la parola “navigazione”, in quanto gli screen reader leggeranno già questa parola. Quindi, un <nav aria-label=”Principale”> dovrebbe venire letto dagli screen reader già come “Navigazione principale”
- I link inseriti nel menù devono avere un testo descrittivo e dovrebbero evitare voci come “Scopri di più”, “Leggi tutto” e simili
La struttura HTML suggerita per creare un menù di navigazione prevede i seguenti elementi:
nav > ul > li > a
Possiamo vedere gli esempi di Navbar creati da Bootstrap per avere un’idea base.
Pulsanti di apertura (dropdown)
Un altro elemento importante che spesso fa parte degli elementi di navigazione è il pulsante di apertura, che io generalmente chiamo “handler”, e può essere usato per attivare o disattivare il menù. Pensiamo al caso di un menù che si apre a tendina (come i dropdown o soluzioni simili) oppure al caso di una navigazione che viene nascosta da mobile, e che viene aperta solo dopo aver attivato il pulsante.
Questa strada prevede di avere un elemento, che generalmente è un <button> ma potrebbe essere anche un <input type=”checkbox”> senza attributo “name”, il quale permette di catturare il click o l’azione dell’utente e scegliere se mostrare o nascondere il menù.
Ecco un esempio semplificato, preso da un caso reale di un sito web che ho sviluppato e che usa WordPress e Bootstrap Italia:
<nav aria-label="Principale" class="navbar navbar-expand-lg main-nav has-megamenu" id="menu-principale">
<ul class="navbar-nav dl-menu nav-list nav-list-primary" data-element="menu">
<li class="nav-item dropdown">
<button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" id="mainNavDropdownDesktopHandler1" data-focus-mouse="false" aria-label="Apri/chiudi menù di: Chi siamo" data-bs-target="mainNavDropdownDesktop1">
<span class="text">Chi siamo</span>
<svg role="img" class="icon icon-xs ms-1"><use href="sprites.svg#it-expand"></use></svg>
</button>
<div id="mainNavDropdownDesktop1" class="dropdown-menu link-list-wrapper">
<ul aria-labelledby="mainNavDropdownDesktop1" class="link-list">
<li><a class="list-item" href="#">Testo del link 1</a></li>
<li><a class="list-item" href="#">Testo del link 2</a></li>
</ul>
</div>
</li>
</ul>
</nav>
In questo esempio possiamo trovare gli elementi principali che ci interessano:
- Il <nav> con attributo aria-label e con le relative classi di Bootstrap
- <ul> e <li> con le relative classi Bootstrap, in particolare la classe “dropdown” per l’elemento <li>
- Il <button> che viene usato come attivatore del menù a tendina (dropdown) tramite Javascript, molto importante da tenere presente è l’uso dell’attributo aria-expanded, che viene usato per indicare alle tecnologie assistive se questo elemento di controllo è stato azionato o meno e se quindi gli elementi collegati vengono mostrati oppure no
- Il sottomenù contenuto in un <div> che contiene i link del nostro menù di navigazione, che in questo caso è quindi di secondo livello
Se volessimo replicare una struttura simile senza l’uso di Bootstrap, quindi per una soluzione completamente custom, dovremmo replicare almeno il comportamento che modifica il valore dell’attributo aria-expanded e poi mostrare o nascondere il menù. Molto banalmente potremmo fare una cosa del genere in Javascript e in CSS:
<nav aria-label="Contestuale">
<button onclick="toggleMenu(this)" data-menu-id="menu-1" aria-expanded="false">Apri/chiudi menù 1</button>
<ul id="menu-1">
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
</ul>
<button onclick="toggleMenu(this)" data-menu-id="menu-2" aria-expanded="false">Apri/chiudi menù 2</button>
<ul id="menu-2">
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
</ul>
</nav>
<style>
nav ul { display:none; }
input[type="checkbox"]:checked ~ ul { display:block; }
.d-none { display:none; }
.d-block { display:block; }
</style>
<script>
function toggleMenu(element) {
let selector = "#" + element.dataset.menuId;
let menu = document.querySelectorAll(selector)[0];
if (element.getAttribute('aria-expanded') == 'true') {
// Menu is already open, let's close it
menu.classList.add('d-none');
menu.classList.remove('d-block');
element.setAttribute('aria-expanded', false);
} else {
// Menu is closed, let's open it
menu.classList.add('d-block');
menu.classList.remove('d-none');
element.setAttribute('aria-expanded', true);
}
}
</script>
Ora, chiaramente l’esempio è molto basilare ma è utile per capire il concetto: mostriamo o nascondiamo il menù quando il pulsante viene azionato e modifichiamo l’attributo aria-expanded.
L’uso di aria-expanded
Per selezionare il menù corretto tramite Javascript, facciamo uso di un data-attribute, che è un attributo HTML che non ha valore semantico ma è molto utile per passare dei valori a Javascript.
La modifica dell’aria-expanded richiede per forza di cose l’uso di Javascript, ma in ogni caso potremmo optare anche per una soluzione con un checkbox invece del button, e mostrare il menù quando questo checkbox è selezionato:
<nav aria-label="Contestuale">
<div class="menu-wrapper">
<input id="menu-1-handler" class="visually-hidden" type="checkbox" onchange="toggleAriaExpanded(this)" aria-expanded="false">
<label for="menu-1-handler"><span class="visually-hidden">Apri/chiudi menù 1</span></label>
<ul id="menu-1">
<li><a href="#">Link 1</a></li>
<li><a href="#">Link 2</a></li>
</ul>
</div>
<div class="menu-wrapper">
<input id="menu-2-handler" class="visually-hidden" type="checkbox" onchange="toggleAriaExpanded(this)" aria-expanded="false">
<label for="menu-2-handler"><span class="visually-hidden">Apri/chiudi menù 2</span></label>
<ul id="menu-2">
<li><a href="#">Link 3</a></li>
<li><a href="#">Link 4</a></li>
</ul>
</div>
</nav>
<style>
nav ul { display:none; }
input[type="checkbox"]:checked ~ ul { display:block; }
</style>
<script>
function toggleAriaExpanded(element) {
if (element.getAttribute('aria-expanded') == 'true') {
element.setAttribute('aria-expanded', false);
} else {
element.setAttribute('aria-expanded', true);
}
// Or, with jQuery (we need to include its files first tho):
let ariaExpanded = jQuery(element).attr('aria-expanded');
if (ariaExpanded == 'true') {
jQuery(element).attr('aria-expanded', false);
} else {
jQuery(element).attr('aria-expanded', true);
}
}
</script>
In questo secondo esempio, usiamo Javascript solo per impostare aria-expanded in modo dinamico, e CSS per nascondere i menu <ul> di default, mostrandoli solo quando il checkbox viene selezionato. L’evento onchange ci serve proprio per capire quando viene modificato lo stato del checkbox.
Ho anche inserito una variante alternativa di codice Javascript fatta con jQuery, framework che viene spesso usato quando si lavora con WordPress, così da fornire un caso più completo.
Link utili
- MDN: Aria Navigation Role
- Bootstrap: Navbar
- Bootstrap: Dropdowns
- MDN: Data attributes