Costruire sistemi di menu accessibili

Pubblicato: 2022-03-10
Riassunto veloce ↬ Ci sono molti tipi diversi di menu sul web. Creare esperienze inclusive è una questione di usare i giusti modelli di menu nei posti giusti, con il giusto markup e comportamento.

Nota del redattore : questo articolo è apparso originariamente su Inclusive Components. Se desideri saperne di più su articoli di componenti inclusivi simili, segui @inclusicomps su Twitter o iscriviti al feed RSS. Supportando inclusive-components.design su Patreon, puoi contribuire a renderlo il database più completo disponibile di componenti di interfaccia robusti.

La classificazione è difficile. Prendi i granchi, per esempio. I granchi eremiti, i granchi di porcellana e i granchi a ferro di cavallo non sono, tassonomicamente parlando, dei veri granchi. Ma questo non ci impedisce di usare il suffisso "granchio". Diventa più confuso quando, nel tempo e grazie a un processo chiamato carcinizzazione , i granchi falsi si evolvono per assomigliare più da vicino ai veri granchi. Questo è il caso dei granchi reali, che si ritiene siano stati granchi eremiti in passato. Immagina le dimensioni dei loro gusci!

Nel design, spesso commettiamo lo stesso errore di dare lo stesso nome a cose diverse. Sembrano simili, ma le apparenze possono ingannare. Ciò può avere un effetto spiacevole sulla chiarezza della libreria dei componenti. In termini di inclusione, può anche portarti a riproporre una componente semanticamente e comportamentalmente inappropriata. Gli utenti si aspetteranno una cosa e ne otterranno un'altra.

Il termine "menu a discesa" nomina un classico esempio. Molte cose "scendono" nelle interfacce, incluso l'insieme di <option> s da un elemento <select> e l'elenco di collegamenti rivelato da JavaScript che costituisce un sottomenu di navigazione. Stesso nome; cose abbastanza diverse. (Alcune persone chiamano questi "pulldown", ovviamente, ma non entriamo in questo.)

I menu a discesa che costituiscono un insieme di opzioni sono spesso chiamati "menu" e voglio parlarne qui. Elaboreremo un vero menu, ma c'è molto da dire su menu non veritieri lungo la strada.

Altro dopo il salto! Continua a leggere sotto ↓

Iniziamo con un quiz. La casella dei collegamenti che pende dalla barra di navigazione nell'illustrazione è un menu?

Una barra di navigazione con include un collegamento al negozio, sotto il quale è appeso un set di tre ulteriori collegamenti rispettivamente a costumi per cani, piastre per cialde e sfere magiche.
Una barra di navigazione con include un collegamento al negozio, sotto il quale è appeso un set di tre ulteriori collegamenti rispettivamente a costumi per cani, piastre per cialde e sfere magiche. (Grande anteprima)

La risposta è no, non un vero menu.

È una convenzione di vecchia data che gli schemi di navigazione siano composti da elenchi di collegamenti. Una convenzione quasi altrettanto vecchia impone che la navigazione secondaria debba essere fornita come elenchi di collegamenti nidificati . Se dovessi rimuovere il CSS per il componente illustrato sopra, dovrei vedere qualcosa di simile al seguente, tranne colorato di blu e in Times New Roman.

Semanticamente parlando, gli elenchi nidificati di collegamenti sono corretti in questo contesto. I sistemi di navigazione sono in realtà dei sommari ed è così che sono strutturati i sommari. L'unica cosa che ci fa davvero pensare al "menu" è lo stile delle liste nidificate e il modo in cui vengono rivelate al passaggio del mouse o al focus.

È qui che alcuni sbagliano e iniziano ad aggiungere la semantica WAI-ARIA: aria-haspopup="true" , role="menu" , role="menuitem" ecc. C'è un posto per questi, come tratteremo, ma non qui . Ecco due motivi per cui:

  1. I menu ARIA non sono designati per la navigazione ma per il comportamento dell'applicazione. Immagina il sistema di menu per un'applicazione desktop.
  2. Il collegamento di primo livello dovrebbe essere utilizzabile come collegamento , il che significa che non si comporta come un pulsante di menu.

Riguardo a (2): quando si attraversa una regione di navigazione con sottomenu, ci si aspetterebbe che ogni sottomenu appaia passando il mouse o mettendo a fuoco il collegamento "livello superiore" ("Shop" nell'illustrazione). Questo rivela il sottomenu e posiziona i propri collegamenti in ordine di messa a fuoco. Con un piccolo aiuto da JavaScript che cattura lo stato attivo e sfoca gli eventi per mantenere l'aspetto dei sottomenu quando necessario, qualcuno che usa la tastiera dovrebbe essere in grado di scorrere ogni collegamento di ogni livello, a turno.

I pulsanti di menu che accettano la proprietà aria-haspopup="true" non si comportano in questo modo. Si attivano con un clic e non hanno altro scopo se non quello di rivelare un menu segreto.

ebook sconti bieten wir
A sinistra: un pulsante del menu denominato "menu" con un'icona a forma di freccia rivolta verso il basso e lo stato aria-expanded = false. A destra: lo stesso pulsante del menu ma con il menu aperto. Questo pulsante è nello stato aria-expanded = true. (Grande anteprima)

Come nella foto, se quel menu è aperto o chiuso dovrebbe essere comunicato con aria-expanded . Dovresti cambiare questo stato solo al clic, non alla messa a fuoco. Gli utenti di solito non si aspettano un cambio di stato esplicito su un semplice evento di focus. Nel nostro sistema di navigazione, lo stato non cambia realmente; è solo un trucco di stile. Comportamentalmente, possiamo scorrere la navigazione come se non si verificassero tali trucchi per mostrare/nascondere.

Il problema con i sottomenu di navigazione

I sottomenu di navigazione (o "menu a discesa" per alcuni) funzionano bene con il mouse o con la tastiera, ma non sono così caldi quando si tratta di toccare. Quando premi per la prima volta il link "Shop" di primo livello nel nostro esempio, gli stai dicendo di aprire il sottomenu e di seguire il link.

Ci sono due possibili risoluzioni qui:

  1. Impedisci il comportamento predefinito dei collegamenti di primo livello ( e.preventDefault() ) e dello script nella semantica e nel comportamento del menu WAI-ARIA completi.
  2. Assicurati che ogni pagina di destinazione di primo livello abbia un sommario in alternativa al sottomenu.

(1) è insoddisfacente perché, come ho notato in precedenza, questo tipo di semantica e comportamenti non sono previsti in questo contesto, dove i collegamenti sono i controlli del soggetto. Gli utenti Plus non possono più accedere a una pagina di primo livello, se esiste.

Nota a margine: quali dispositivi sono dispositivi touch?

Si è tentati di pensare "questa non è un'ottima soluzione, ma la aggiungerò solo per le interfacce touch". Il problema è: come si fa a rilevare se un dispositivo ha un touch screen?

Certamente non dovresti equiparare "schermo piccolo" a "touch attivato". Avendo lavorato nello stesso ufficio delle persone che realizzano display touch per musei, posso assicurarti che alcuni degli schermi più grandi in circolazione sono touch screen. Anche i laptop con doppia tastiera e input touch stanno diventando sempre più prolifici.

Allo stesso modo, molti ma non tutti i dispositivi più piccoli sono dispositivi touch. Nella progettazione inclusiva, non puoi permetterti di fare supposizioni.

La risoluzione (2) è più inclusiva e solida in quanto fornisce un "fallback" per gli utenti di tutti gli input. Ma le citazioni spaventose intorno al termine di riserva qui sono piuttosto deliberate perché in realtà penso che i sommari in-page siano un modo migliore per fornire la navigazione.

Il premiato team dei servizi digitali del governo sembrerebbe d'accordo. Potresti averli visti anche su Wikipedia.

I sommari di Gov.uk sono minimi con i trattini come stili di elenco. Wikipedia fornisce una casella grigia bordata con elementi numerati. Entrambi sono contenuti etichettati.
I sommari di Gov.uk sono minimi con i trattini come stili di elenco. Wikipedia fornisce una casella grigia bordata con elementi numerati. Entrambi sono contenuti etichettati.

Tabelle dei contenuti

I sommari sono la navigazione per pagine correlate o sezioni di pagina e dovrebbero essere semanticamente simili alle principali aree di navigazione del sito, utilizzando un elemento <nav> , un elenco e un meccanismo di etichettatura di gruppo.

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->

Appunti

  • In questo esempio, immaginiamo che ogni sezione sia la sua pagina, come sarebbe stato nel sottomenu a discesa.
  • È importante che ciascuna di queste pagine "Shop" abbia la stessa struttura, con questo sommario "Prodotti" presente nello stesso luogo. La coerenza supporta la comprensione.
  • L'elenco raggruppa gli elementi e li enumera nell'output della tecnologia assistiva, ad esempio la voce sintetica di un lettore di schermo.
  • Il <nav> è etichettato ricorsivamente dall'intestazione usando aria-labelledby . Ciò significa che la "navigazione dei prodotti" verrà annunciata nella maggior parte dei lettori di schermo all'ingresso nella regione tramite Tab . Significa anche che la "navigazione dei prodotti" sarà dettagliata nelle interfacce degli elementi dello screen reader, da cui gli utenti possono navigare direttamente nelle regioni.

Tutto su una pagina

Se riesci a inserire tutte le sezioni in una pagina senza che diventi troppo lungo e arduo da scorrere, ancora meglio. Basta collegarsi all'identificatore hash di ogni sezione. Ad esempio, href="#waffle-irons" dovrebbe puntare a .

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->

( Nota: alcuni browser non riescono effettivamente a inviare lo stato attivo ai frammenti di pagina collegati. L'inserimento di tabindex="-1" sul frammento di destinazione risolve questo problema.)

Laddove un sito ha molti contenuti, un'architettura dell'informazione accuratamente costruita, espressa attraverso l'uso liberale di "menu" di sommari è infinitamente preferibile a un sistema a tendina precario e ingombrante. Non solo è più facile rendere reattivo e richiede meno codice per farlo, ma rende le cose più chiare: dove i sistemi a discesa nascondono la struttura, le tabelle dei contenuti la mettono a nudo.

Alcuni siti, incluso gov.uk del Government Digital Service, includono pagine di indice (o "argomento") che sono solo sommari. È un concetto così potente che il popolare generatore di siti statici Hugo genera tali pagine per impostazione predefinita.

Diagramma dello stile dell'albero genealogico con pagina di destinazione dell'argomento in alto con due propaggini della pagina individuale. Ciascuna delle singole propaggini di pagina ha più propaggini di sezioni di pagina
Diagramma dello stile dell'albero genealogico con pagina di destinazione dell'argomento in alto con due propaggini della pagina individuale. Ciascuna delle singole derivazioni di pagina ha più derivazioni di sezioni di pagina (anteprima grande)

L'architettura dell'informazione è una parte importante dell'inclusione. Un sito mal organizzato può essere tecnicamente conforme quanto vuoi, ma alienerà comunque molti utenti, specialmente quelli con problemi cognitivi o quelli che hanno poco tempo.

Pulsanti del menu di navigazione

Mentre siamo in tema di falsi menu relativi alla navigazione, sarebbe negligente da parte mia non parlare dei pulsanti del menu di navigazione. Quasi sicuramente li hai visti indicati da un'icona "hamburger" o "navicon" a tre righe.

Anche con un'architettura delle informazioni ridotta e un solo livello di collegamenti di navigazione, lo spazio sui piccoli schermi è prezioso. Nascondere la navigazione dietro un pulsante significa che c'è più spazio per il contenuto principale nella finestra.

Un pulsante di navigazione è la cosa più vicina che abbiamo studiato finora a un vero pulsante di menu. Poiché ha lo scopo di alternare la disponibilità di un menu al clic, dovrebbe:

  1. Si identifica come un pulsante, non un collegamento;
  2. Identifica lo stato espanso o compresso del menu corrispondente (che, in termini rigorosi, è solo un elenco di collegamenti).

Miglioramento progressivo

Ma non andiamo avanti a noi stessi. Dovremmo essere consapevoli del miglioramento progressivo e considerare come funzionerebbe senza JavaScript.

In un documento HTML non migliorato non c'è molto che puoi fare con i pulsanti (tranne i pulsanti di invio ma non è nemmeno strettamente correlato a ciò che vogliamo ottenere qui). Dovremmo, invece, partire da solo un link che ci porti alla navigazione?

 <a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

Non ha molto senso avere il collegamento a meno che non ci sia molto contenuto tra il collegamento e la navigazione. Poiché la navigazione del sito dovrebbe apparire quasi sempre nella parte superiore dell'ordine di origine, non è necessario. Quindi, in realtà, un menu di navigazione in assenza di JavaScript dovrebbe essere solo... un po' di navigazione.

 <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

Lo migliori aggiungendo il pulsante, nel suo stato iniziale, e nascondendo la navigazione (usando l'attributo hidden ):

 <nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

Alcuni browser meno recenti - sai quali - non supportano hidden , quindi ricorda di inserire quanto segue nel tuo CSS. Risolve il problema perché display: none ha lo stesso effetto di nascondere il menu dalle tecnologie assistive e rimuovere i collegamenti dall'ordine di messa a fuoco.

 [hidden] { display: none; }

Fare del proprio meglio per supportare il software meno recente è, ovviamente, un atto di progettazione inclusiva. Alcuni non sono in grado o non vogliono eseguire l'aggiornamento.

Posizionamento

Il punto in cui molte persone sbagliano è piazzando il pulsante fuori dalla regione. Ciò significherebbe che gli utenti di screen reader che si spostano su <nav> utilizzando un collegamento lo troverebbero vuoto, il che non è molto utile. Con l'elenco nascosto agli screen reader, incontrerebbero semplicemente questo:

 <nav> </nav>

Ecco come possiamo commutare lo stato:

 var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });

aria-controlli

Come ho scritto in Aria-controls Is Poop, l'attributo aria-controls , inteso ad aiutare gli utenti dello screen reader a passare da un elemento di controllo a un elemento controllato, è supportato solo nello screen reader JAWS. Quindi semplicemente non puoi fare affidamento su di esso.

Senza un buon metodo per indirizzare gli utenti tra gli elementi, dovresti invece assicurarti che uno dei seguenti sia vero:

  1. Il primo collegamento dell'elenco espanso è il successivo nell'ordine di messa a fuoco dopo il pulsante (come nell'esempio di codice precedente).
  2. Il primo collegamento è focalizzato a livello di codice sulla rivelazione dell'elenco.

In questo caso, consiglierei (1). È molto più semplice poiché non devi preoccuparti di riportare l'attenzione sul pulsante e su quali eventi farlo. Inoltre, al momento non c'è nulla in atto per avvisare gli utenti che la loro attenzione verrà spostata in un luogo diverso. Nei menu true di cui parleremo a breve, questo è il lavoro di aria-haspopup="true" .

L' aria-controls non fa molto male, tranne per il fatto che rende più prolissa la lettura negli screen reader. Tuttavia, alcuni utenti di JAWS potrebbero aspettarselo. Ecco come verrebbe applicato, usando l' id della lista come cifra:

 <nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

I ruoli del menu e delle voci di menu

Un vero menu (nel senso WAI-ARIA) dovrebbe identificarsi come tale usando il ruolo menu (per il contenitore) e, tipicamente, menuitem figlio (possono essere applicati altri ruoli figlio). Questi ruoli genitore e figlio lavorano insieme per fornire informazioni alle tecnologie assistive. Ecco come un elenco potrebbe essere aumentato per avere la semantica del menu:

 <ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>

Dal momento che il nostro menu di navigazione inizia a comportarsi in qualche modo come un menu "vero", questi non dovrebbero essere presenti?

La risposta breve è: no. La risposta lunga è: no, perché le voci del nostro elenco contengono collegamenti e gli elementi menuitem non sono destinati ad avere discendenti interattivi. Cioè, sono i controlli in un menu.

Potremmo, ovviamente, sopprimere la semantica dell'elenco dei <li> s usando role="presentation" o role="none" (che sono equivalenti) e posizionare il ruolo menuitem su ciascun collegamento. Tuttavia, ciò sopprimerebbe il ruolo di collegamento implicito. In altre parole, l'esempio da seguire verrebbe annunciato come “Home, voce di menu”, non “Home, link” o “Home, voce di menu, link”. I ruoli ARIA sostituiscono semplicemente i ruoli HTML.

 <!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>

Vogliamo che l'utente sappia che sta utilizzando un collegamento e può aspettarsi un comportamento del collegamento, quindi questo non va bene. Come ho detto, i menu veri sono per il comportamento dell'applicazione (basato su JavaScript).

Quello che ci resta è una sorta di componente ibrido, che non è proprio un vero menu ma almeno dice agli utenti se l'elenco dei collegamenti è aperto, grazie allo stato di aria-expanded . Questo è un modello perfettamente soddisfacente per i menu di navigazione.

Nota a margine: l'elemento <select>

Se sei stato coinvolto nel design reattivo fin dall'inizio, potresti ricordare uno schema per cui la navigazione era condensata in un elemento <select> per finestre strette.

portatile con elemento selezionato che mostra "casa" selezionato nella parte superiore del viewport
Portatile con elemento selezionato che mostra "casa" selezionato nella parte superiore della finestra.

Come per i pulsanti di attivazione/disattivazione basati su caselle di controllo di cui abbiamo discusso, l'utilizzo di un elemento nativo che si comporta in qualche modo come previsto senza script aggiuntivi è una buona scelta per l'efficienza e, soprattutto sui dispositivi mobili, per le prestazioni. E gli elementi <select> sono una sorta di menu, con una semantica simile al menu attivato da un pulsante che costruiremo presto.

Tuttavia, proprio come con il pulsante di attivazione/disattivazione della casella di controllo, stiamo utilizzando un elemento associato all'immissione di input, non semplicemente a una scelta. È probabile che ciò crei confusione a molti utenti, soprattutto perché questo modello utilizza JavaScript per fare in modo che l' <option> selezionata si comporti come un collegamento. L'inaspettato cambiamento di contesto che ciò provoca è considerato un fallimento secondo il criterio 3.2.2 On Input (Livello A) delle WCAG.

Menu veri

Ora che abbiamo discusso di menu falsi e quasi menu, è giunto il momento di creare un menu vero , aperto e chiuso da un pulsante menu vero. Da qui in poi mi riferirò al pulsante e al menu insieme semplicemente come un "pulsante del menu".

Ma sotto quali aspetti il ​​nostro pulsante del menu sarà vero? Bene, sarà un componente di menu destinato alla scelta delle opzioni nell'applicazione in oggetto, che implementa tutta la semantica prevista e i comportamenti corrispondenti da considerare convenzionali per uno strumento del genere.

Come già accennato, queste convenzioni derivano dalla progettazione di applicazioni desktop. Per imitarli completamente sono necessari l'attribuzione ARIA e la gestione del focus governata da JavaScript. Parte dello scopo di ARIA è aiutare gli sviluppatori web a creare esperienze web ricche senza rompere con le convenzioni di usabilità forgiate nel mondo nativo.

In questo esempio, immaginiamo che la nostra applicazione sia una sorta di gioco o quiz. Il nostro pulsante menu consentirà all'utente di scegliere un livello di difficoltà. Con tutta la semantica in atto, il menu si presenta così:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>

Appunti

  • La proprietà aria-haspopup indica semplicemente che il pulsante secerne un menu. Agisce come avviso che, se premuto, l'utente verrà spostato nel menu "popup" (a breve tratteremo il comportamento del focus). Il suo valore non cambia: rimane sempre true .
  • Il <span> all'interno del pulsante contiene il punto unicode per un piccolo triangolo nero rivolto verso il basso. Questa convenzione indica visivamente ciò che aria-haspopup fa non visivamente: premendo il pulsante si rivelerà qualcosa sotto di esso. L'attribuzione aria-hidden="true" impedisce ai lettori di schermo di annunciare "triangolo rivolto verso il basso" o simili. Grazie ad aria-haspopup , non è necessario nel contesto non visivo.
  • La proprietà aria-haspopup è completata da aria-expanded . Questo indica all'utente se il menu è attualmente in uno stato aperto (espanso) o chiuso (compresso) alternando tra valori true e false .
  • Il menu stesso assume il ruolo di menu (con il nome appropriato). Ci vogliono discendenti con il ruolo di menuitem . Non è necessario che siano figli diretti dell'elemento menu , ma in questo caso lo sono, per semplicità.

Comportamento della tastiera e della messa a fuoco

Quando si tratta di rendere accessibile la tastiera dei controlli interattivi, la cosa migliore che puoi fare è utilizzare gli elementi giusti. Poiché qui stiamo usando gli elementi <button> , possiamo essere certi che gli eventi click si attiveranno sui tasti Invio e Spazio , come specificato nell'interfaccia HTMLButtonElement. Significa anche che possiamo disabilitare le voci di menu utilizzando la proprietà disabled associata al pulsante.

Tuttavia, c'è molto di più nell'interazione della tastiera dei pulsanti del menu. Ecco un riepilogo di tutto il focus e il comportamento della tastiera che implementeremo, basato su WAI-ARIA Authoring Practices 1.1:

Immettere , Spazio o sul pulsante del menu Apre il menu
su una voce di menu Sposta lo stato attivo sulla voce di menu successiva o sulla prima voce di menu se sei sull'ultima
su una voce di menu Sposta lo stato attivo sulla voce di menu precedente o sull'ultima voce di menu se sei sulla prima
sul pulsante del menu Chiude il menu se aperto
Esc su una voce di menu Chiude il menu e mette a fuoco il pulsante del menu

Il vantaggio di spostare lo stato attivo tra le voci di menu utilizzando i tasti freccia è che Tab viene mantenuto per uscire dal menu. In pratica, ciò significa che gli utenti non devono spostarsi attraverso ogni voce di menu per uscire dal menu, un enorme miglioramento per l'usabilità, soprattutto dove ci sono molte voci di menu.

L'applicazione di tabindex="-1" rende le voci di menu non focalizzabili da Tab ma conserva la possibilità di mettere a fuoco gli elementi a livello di codice, dopo aver catturato le battute dei tasti sui tasti freccia.

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>

Il metodo aperto

Come parte di una solida progettazione API, possiamo costruire metodi per gestire i vari eventi.

Ad esempio, il metodo open deve cambiare il valore di aria-expanded su "true", modificare la proprietà nascosta del menu su false e mettere a fuoco la prima menuitem di menu nel menu che non è disabilitata:

 MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }

Possiamo eseguire questo metodo in cui l'utente preme il tasto giù su un'istanza del pulsante del menu focalizzata:

 this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));

Inoltre, uno sviluppatore che utilizza questo script sarà ora in grado di aprire il menu a livello di codice:

 exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();

Nota a margine: la casella di controllo hack

Per quanto possibile, è meglio non utilizzare JavaScript a meno che non sia necessario. Coinvolgere una terza tecnologia oltre a HTML e CSS è necessariamente un aumento della complessità e fragilità sistemiche. Tuttavia, non tutti i componenti possono essere compilati in modo soddisfacente senza JavaScript nel mix.

Nel caso dei pulsanti dei menu, l'entusiasmo nel farli "funzionare senza JavaScript" ha portato a qualcosa chiamato hack della casella di controllo. È qui che lo stato selezionato (o deselezionato) di una casella di controllo nascosta viene utilizzato per attivare o disattivare la visibilità di un elemento di menu utilizzando CSS.

 /* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }

Per gli utenti di screen reader, il ruolo della casella di controllo e lo stato selezionato non hanno senso in questo contesto. Questo può essere in parte superato aggiungendo role="button" alla casella di controllo.

 <input type="checkbox" role="button" aria-haspopup="true">

Sfortunatamente, questo sopprime la comunicazione implicita dello stato controllato, privandoci del feedback dello stato privo di JavaScript (sebbene sarebbe stato "controllato" in questo contesto).

Ma è possibile falsificare aria-expanded . Abbiamo solo bisogno di fornire la nostra etichetta con due campate come di seguito.

 <input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded&lt;/span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">&#x25be;</span> </label>

Questi sono entrambi visivamente nascosti usando la classe visually-hidden , ma, a seconda dello stato in cui ci troviamo, solo uno è nascosto anche agli screen reader. Cioè, solo uno ha display: none , e questo è determinato dallo stato verificato esistente (ma non comunicato):

 /* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }

Questo è intelligente e tutto, ma il nostro pulsante del menu è ancora incompleto poiché i comportamenti di messa a fuoco previsti di cui abbiamo discusso semplicemente non possono essere implementati senza JavaScript.

Questi comportamenti sono convenzionali e previsti, rendendo il pulsante più utilizzabile. Tuttavia, se hai davvero bisogno di implementare un pulsante di menu senza JavaScript, questo è il più vicino possibile. Considerando che il pulsante del menu di navigazione ridotto di cui ho parlato in precedenza offre contenuti di menu che non dipendono da JavaScript di per sé (es. collegamenti), questo approccio potrebbe essere un'opzione adatta.

Per divertimento, ecco una codePen che implementa un pulsante del menu di navigazione senza JavaScript.

Vedere l'esempio del pulsante del menu di navigazione della penna senza JS di Heydon (@heydon) su CodePen.

( Nota: solo lo spazio apre il menu.)

L'evento “scegli”.

L'esecuzione di alcuni metodi dovrebbe emettere eventi in modo da poter impostare listener. Ad esempio, possiamo emettere un evento choose quando un utente fa clic su una voce di menu. Possiamo configurarlo utilizzando CustomEvent , che ci consente di passare un argomento alla proprietà di detail dell'evento. In questo caso, l'argomento ("scelta") sarebbe il nodo DOM della voce di menu scelta.

 MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }

Ci sono un sacco di cose che possiamo fare con questo meccanismo. Forse abbiamo una regione live impostata con un id di menuFeedback :

 <div role="alert"></div>

Ora possiamo impostare un listener e popolare la regione live con le informazioni segrete all'interno dell'evento:

 exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
Quando un utente sceglie un'opzione, il menu si chiude e lo stato attivo torna al pulsante del menu. È importante che gli utenti tornino all'elemento di attivazione dopo la chiusura del menu.
Quando un utente sceglie un'opzione, il menu si chiude e lo stato attivo torna al pulsante del menu. È importante che gli utenti tornino all'elemento di attivazione dopo la chiusura del menu. (Grande anteprima)

Quando viene selezionata una voce di menu, l'utente dell'utilità per la lettura dello schermo sentirà "Hai scelto [etichetta della voce di menu]" . Una regione attiva (definita qui con l'attribuzione role=“alert” ) annuncia il proprio contenuto negli screen reader ogni volta che il contenuto cambia. La regione live non è obbligatoria, ma è un esempio di ciò che potrebbe accadere nell'interfaccia in risposta all'utente che effettua una scelta di menu.

Scelte persistenti

Non tutte le voci di menu consentono di scegliere le impostazioni persistenti. Molti si comportano semplicemente come pulsanti standard che fanno accadere qualcosa nell'interfaccia quando vengono premuti. Tuttavia, nel caso del nostro pulsante del menu di difficoltà, vorremmo indicare quale è l'impostazione di difficoltà attuale, quella scelta per ultima.

L'attributo aria-checked="true" funziona per gli elementi che, invece di menuitem , assumono il ruolo menuitemradio . Il markup avanzato, con il secondo elemento selezionato ( set ) è simile al seguente:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>

I menu nativi su molte piattaforme indicano gli elementi scelti tramite segni di spunta. Possiamo farlo senza problemi usando un piccolo CSS in più:

 [role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }

Durante l'attraversamento del menu con un'utilità per la lettura dello schermo in esecuzione, mettendo a fuoco questa voce selezionata verrà visualizzato un annuncio come "segno di spunta, voce di menu media, selezionata" .

Il comportamento all'apertura di un menu con una voce di menu menuitemradio differisce leggermente. Invece di mettere a fuoco il primo elemento (abilitato) nel menu, viene invece messo a fuoco l'elemento selezionato .

Il pulsante del menu si avvia con il menu non aperto. All'apertura viene focalizzata la seconda impostazione di difficoltà (Media). È preceduto da un segno di spunta in base alla presenza dell'attributo aria-checked.
Il pulsante del menu si avvia con il menu non aperto. All'apertura viene focalizzata la seconda impostazione di difficoltà (Media). È preceduto da un segno di spunta in base alla presenza dell'attributo aria-checked. (Grande anteprima)

Qual è il vantaggio di questo comportamento? L'utente (qualsiasi utente) viene ricordato dell'opzione precedentemente selezionata. Nei menu con numerose opzioni incrementali (ad esempio, un insieme di livelli di zoom), le persone che operano tramite tastiera vengono poste nella posizione ottimale per effettuare la propria regolazione.

Utilizzo del pulsante Menu con uno screen reader

In questo video ti mostrerò com'è usare il pulsante del menu con lo screen reader Voiceover e Chrome. L'esempio utilizza elementi con menuitemradio , aria-checked e il comportamento del focus discusso. Esperienze simili possono essere previste in tutta la gamma di popolari software di lettura dello schermo.

Pulsante Menu inclusivo su Github

Kitty Giraudel e io abbiamo lavorato insieme alla creazione di un componente del pulsante del menu con le funzionalità dell'API che ho descritto e altro ancora. Devi ringraziare Hugo per molte di queste funzionalità, dal momento che erano basate sul lavoro svolto su a11y-dialog, una finestra di dialogo modale accessibile. È disponibile su Github e NPM.

 npm i inclusive-menu-button --save

Inoltre, Kitty ha creato una versione React per la tua gioia.

Lista di controllo

  • Non utilizzare la semantica del menu ARIA nei sistemi di menu di navigazione.
  • Sui siti con contenuti pesanti, non nascondere la struttura nei menu di navigazione nidificati a discesa.
  • Usa aria-expanded per indicare lo stato aperto/chiuso di un menu di navigazione attivato da un pulsante.
  • Assicurati che il menu di navigazione sia il successivo in ordine di messa a fuoco dopo il pulsante che lo apre/chiude.
  • Non sacrificare mai l'usabilità nella ricerca di soluzioni prive di JavaScript. È vanità.