Construirea de sisteme de meniu accesibile

Publicat: 2022-03-10
Rezumat rapid ↬ Există o mulțime de tipuri diferite de meniuri pe web. Crearea de experiențe incluzive este o chestiune de a folosi modelele de meniu potrivite în locurile potrivite, cu marcajul și comportamentul potrivit.

Nota editorului : Acest articol a apărut inițial pe Inclusive Components. Dacă doriți să aflați mai multe despre articole similare cu componente inclusive, urmăriți @inclusicomps pe Twitter sau abonați-vă la fluxul RSS. Prin sprijinirea inclusive-components.design pe Patreon, puteți contribui la transformarea acesteia în cea mai cuprinzătoare bază de date de componente robuste de interfață disponibilă.

Clasificarea este grea. Luați crabii, de exemplu. Crabii pustnici, crabii de porțelan și crabii potcoave nu sunt – taxonomic vorbind – crabi adevărați . Dar asta nu ne împiedică să folosim sufixul „crab”. Devine mai confuz când, de-a lungul timpului și datorită unui proces numit carcinizare , crabii neadevărați evoluează pentru a semăna mai îndeaproape cu crabii adevărați. Acesta este cazul crabilor rege, despre care se crede că au fost crabi pustnici în trecut. Imaginează-ți dimensiunea cochiliilor lor!

În design, facem adesea aceeași greșeală de a da diferitelor lucruri același nume. Par asemănătoare, dar aparențele pot fi înșelătoare. Acest lucru poate avea un efect nefericit asupra clarității bibliotecii dvs. de componente. În ceea ce privește includerea, vă poate conduce, de asemenea, să reutilizați o componentă inadecvată din punct de vedere semantic și comportamental. Utilizatorii se vor aștepta la un lucru și vor primi altul.

Termenul „dropdown” numește un exemplu clasic. O mulțime de lucruri „se derulează” în interfețe, inclusiv setul de <option> -uri dintr-un element <select> și lista de linkuri dezvăluită de JavaScript care constituie un submeniu de navigare. Acelasi nume; lucruri destul de diferite. (Unii oameni numesc aceste „pulldowns”, desigur, dar să nu intrăm în asta.)

Meniurile derulante care constituie un set de opțiuni sunt adesea numite „meniuri” și vreau să vorbesc despre acestea aici. Vom concepe un meniu adevărat , dar sunt multe de spus despre meniurile care nu sunt cu adevărat adevărate pe parcurs.

Mai multe după săritură! Continuați să citiți mai jos ↓

Să începem cu un test. Caseta de linkuri care atârnă în jos din bara de navigare din ilustrație este un meniu?

O bară de navigare cu include un link pentru magazin, dedesubt care atârnă un set de trei link-uri suplimentare către costume pentru câini, fier de călcat și, respectiv, globuri magice.
O bară de navigare cu include un link pentru magazin, dedesubt care atârnă un set de trei link-uri suplimentare către costume pentru câini, fier de călcat și, respectiv, globuri magice. (Previzualizare mare)

Răspunsul este nu, nu este un meniu adevărat.

Este o convenție de lungă durată că schemele de navigare sunt compuse din liste de legături. O convenție aproape la fel de veche impune ca sub-navigarea să fie furnizată ca liste imbricate de legături. Dacă ar fi să elimin CSS-ul pentru componenta ilustrată mai sus, ar trebui să văd ceva de genul următor, cu excepția culorii albastre și în Times New Roman.

Din punct de vedere semantic, listele imbricate de legături sunt corecte în acest context. Sistemele de navigare sunt cu adevărat tabele de conținut și așa sunt structurate tabelele de conținut. Singurul lucru care ne face să gândim cu adevărat „meniu” este stilul listelor imbricate și modul în care acestea sunt dezvăluite la trecerea cu mouse-ul sau la focalizare.

Acolo unii greșesc și încep să adauge semantică WAI-ARIA: aria-haspopup="true" , role="menu" , role="menuitem" etc. Există un loc pentru acestea, așa cum vom trata, dar nu aici . Iată două motive pentru care:

  1. Meniurile ARIA nu sunt desemnate pentru navigare, ci pentru comportamentul aplicației. Imaginați-vă sistemul de meniuri pentru o aplicație desktop.
  2. Linkul de nivel superior ar trebui să poată fi utilizat ca link , ceea ce înseamnă că nu se comportă ca un buton de meniu.

În ceea ce privește (2): Când parcurgeți o regiune de navigare cu submeniuri, ne-am aștepta ca fiecare submeniu să apară la trecerea cu mouse-ul sau focalizarea linkului „nivel superior” („Magazin” în ilustrație). Acest lucru dezvăluie submeniul și își plasează propriile linkuri în ordinea focalizării. Cu puțin ajutor din partea JavaScript pentru captarea focalizării și estomparea evenimentelor pentru a persista aspectul submeniurilor atunci când este necesar, cineva care folosește tastatura ar trebui să poată trece pe rând prin fiecare link al fiecărui nivel.

Butoanele de meniu care au proprietatea aria-haspopup="true" nu se comportă astfel. Ele sunt activate la clic și nu au alt scop decât acela de a dezvălui un meniu secret.

reduceri de cărți electronice bieten wir
Stânga: un buton de meniu etichetat „meniu” cu o pictogramă săgeată în jos și aria extinsă = stare falsă. Dreapta: același buton de meniu, dar cu meniul deschis. Acest buton este în starea aria-expanded = true. (Previzualizare mare)

După cum se arată în imagine, dacă acel meniu este deschis sau închis, ar trebui comunicat cu aria-expanded . Ar trebui să schimbați această stare doar la clic, nu la focalizare. Utilizatorii nu se așteaptă, de obicei, la o schimbare explicită de stare pentru un simplu eveniment de focalizare. În sistemul nostru de navigație, starea nu se schimbă cu adevărat; este doar un truc de styling. Din punct de vedere comportamental, putem trece prin navigare ca și cum nu ar fi avut loc un astfel de truc de afișare/ascundere.

Problema cu submeniurile de navigare

Submeniurile de navigare (sau „menuri derulante” pentru unele) funcționează bine cu un mouse sau cu tastatura, dar nu sunt atât de fierbinți când vine vorba de atingere. Când apăsați pentru prima dată pe linkul de nivel superior „Magazin” din exemplul nostru, îi spuneți să deschidă submeniul și să urmeze linkul.

Există două soluții posibile aici:

  1. Preveniți comportamentul implicit al legăturilor de nivel superior ( e.preventDefault() ) și al scriptului în semantica și comportamentul complet al meniului WAI-ARIA.
  2. Asigurați-vă că fiecare pagină de destinație de nivel superior are un cuprins ca alternativă la submeniu.

(1) este nesatisfăcător deoarece, așa cum am observat anterior, aceste tipuri de semantică și comportamente nu sunt așteptate în acest context, unde legăturile sunt controalele subiectului. În plus, utilizatorii nu mai pot naviga la o pagină de nivel superior, dacă aceasta există.

Notă laterală: Ce dispozitive sunt dispozitive tactile?

Este tentant să te gândești, „aceasta nu este o soluție grozavă, dar o voi adăuga doar pentru interfețele tactile”. Problema este: cum detectăm dacă un dispozitiv are un ecran tactil?

Cu siguranță nu ar trebui să echivalezi „ecran mic” cu „activat prin atingere”. După ce am lucrat în același birou cu oamenii care produc ecrane tactile pentru muzee, vă pot asigura că unele dintre cele mai mari ecrane din jur sunt ecrane tactile. De asemenea, laptopurile cu tastatură duală și cu intrare tactilă devin din ce în ce mai prolifice.

În același mod, multe, dar nu toate dispozitivele mai mici sunt dispozitive tactile. În designul incluziv, nu vă permiteți să faceți presupuneri.

Rezoluția (2) este mai incluzivă și mai robustă prin faptul că oferă o „rezolvare” pentru utilizatorii tuturor intrărilor. Dar citatele înfricoșătoare din jurul termenului alternativ de aici sunt destul de deliberate, deoarece de fapt cred că tabelele de conținut în pagină sunt o modalitate superioară de a oferi navigare.

Echipa premiată a serviciilor digitale guvernamentale pare să fie de acord. S-ar putea să le fi văzut și pe Wikipedia.

Tabelele de conținut Gov.uk sunt minime, cu cratime ca stiluri de listă. Wikipedia oferă o casetă gri mărginită cu elemente numerotate. Ambele sunt conținuturi etichetate.
Tabelele de conținut Gov.uk sunt minime, cu cratime ca stiluri de listă. Wikipedia oferă o casetă gri mărginită cu elemente numerotate. Ambele sunt conținuturi etichetate.

Tabele de conținut

Tabelele de conținut sunt navigare pentru paginile conexe sau secțiunile de pagină și ar trebui să fie similare din punct de vedere semantic cu regiunile principale de navigare a site-ului, folosind un element <nav> , o listă și un mecanism de etichetare a grupurilor.

 <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 -->

Note

  • În acest exemplu, ne imaginăm că fiecare secțiune este propria sa pagină, așa cum ar fi fost în submeniul drop-down.
  • Este important ca fiecare dintre aceste pagini „Magazin” să aibă aceeași structură, cu acest cuprins „Produse” prezent în același loc. Consecvența sprijină înțelegerea.
  • Lista grupează articolele și le enumerează în rezultatul tehnologiei de asistență, cum ar fi vocea sintetică a unui cititor de ecran.
  • <nav> este etichetat recursiv de titlu folosind aria-labelledby . Aceasta înseamnă că „navigarea produselor” va fi anunțată în majoritatea cititoarelor de ecran la intrarea în regiune prin Tab . De asemenea, înseamnă că „navigarea produselor” va fi detaliată în interfețele elementelor cititorului de ecran, din care utilizatorii pot naviga direct în regiuni.

Toate pe o singură pagină

Dacă puteți încadra toate secțiunile într-o singură pagină fără ca derularea să devină prea lungă și dificilă, cu atât mai bine. Doar conectați la identificatorul hash al fiecărei secțiuni. De exemplu, href="#waffle-irons" ar trebui să indice .

 <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 -->

( Notă: unele browsere nu trimit efectiv focalizarea către fragmentele de pagini legate. Plasarea tabindex="-1" pe fragmentul țintă remediază acest lucru.)

Acolo unde un site are mult conținut, o arhitectură informațională atent construită, exprimată prin folosirea liberală a „meniurilor” tabelelor de conținut este infinit de preferată unui sistem derulant precar și greu de manevrat. Nu numai că este mai ușor de făcut receptiv și necesită mai puțin cod pentru a face acest lucru, dar face lucrurile mai clare: acolo unde sistemele drop-down ascund structura, tabelele de conținut o dezvăluie.

Unele site-uri, inclusiv gov.uk al Serviciului Digital Guvernamental, includ pagini de index (sau „teme”) care sunt doar tabele de conținut. Este un concept atât de puternic încât popularul generator de site-uri static Hugo generează astfel de pagini în mod implicit.

Diagrama stilului arborelui genealogic cu pagina de destinație a subiectului în partea de sus cu două ramuri individuale ale paginii. Fiecare dintre ramurile individuale ale paginii au mai multe ramuri de secțiune de pagină
Diagrama stilului arborelui genealogic cu pagina de destinație a subiectului în partea de sus cu două ramuri individuale ale paginii. Fiecare dintre ramurile individuale de pagină are mai multe ramuri de secțiune de pagină (previzualizare mare)

Arhitectura informației este o parte importantă a incluziunii. Un site prost organizat poate fi atât de tehnic pe cât doriți, dar va înstrăina totuși mulți utilizatori, în special pe cei cu deficiențe cognitive sau pe cei care sunt presați de timp.

Butoanele meniului de navigare

În timp ce vorbim despre meniurile false legate de navigare, ar fi neglijent din partea mea să nu vorbesc despre butoanele meniului de navigare. Aproape sigur le-ați văzut pe acestea notate de o pictogramă „hamburger” sau „navicon” cu trei linii.

Chiar și cu o arhitectură informațională redusă și doar un singur nivel de legături de navigare, spațiul pe ecranele mici este un premiu. Ascunderea navigării în spatele unui buton înseamnă că există mai mult spațiu pentru conținutul principal în fereastra de vizualizare.

Un buton de navigare este cel mai apropiat lucru pe care l-am studiat până acum de un adevărat buton de meniu. Deoarece are scopul de a comuta disponibilitatea unui meniu la clic, ar trebui:

  1. Identificați-vă ca un buton, nu un link;
  2. Identificați starea extinsă sau restrânsă a meniului său corespunzător (care, în termeni stricti, este doar o listă de link-uri).

Îmbunătățire progresivă

Dar să nu trecem înaintea noastră. Ar trebui să fim atenți la îmbunătățirea progresivă și să ne gândim cum ar funcționa acest lucru fără JavaScript.

Într-un document HTML neîmbunătățit, nu puteți face multe cu butoanele (cu excepția butoanelor de trimitere, dar asta nici măcar nu este strâns legat de ceea ce vrem să obținem aici). În schimb, poate ar trebui să începem doar cu un link care ne duce la navigare?

 <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>

Nu are mare rost să ai linkul decât dacă există mult conținut între link și navigare. Deoarece navigarea pe site ar trebui să apară aproape întotdeauna în partea de sus a ordinii sursei, nu este nevoie. Deci, într-adevăr, un meniu de navigare în absența JavaScript ar trebui să fie doar... ceva de navigare.

 <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>

Îmbunătățiți acest lucru adăugând butonul, în starea sa inițială, și ascund navigarea (folosind atributul 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>

Unele browsere mai vechi — știți care dintre ele — nu acceptă hidden , așa că nu uitați să puneți următoarele în CSS. Rezolvă problema deoarece display: none are același efect de a ascunde meniul de tehnologiile de asistență și de eliminare a legăturilor din ordinea de focalizare.

 [hidden] { display: none; }

A face tot posibilul pentru a susține software-ul mai vechi este, desigur, un act de proiectare incluzivă. Unii nu pot sau nu doresc să facă upgrade.

Plasarea

Unde mulți oameni greșesc este prin plasarea butonului în afara regiunii. Acest lucru ar însemna că utilizatorii cititorului de ecran care se mută la <nav> folosind o comandă rapidă ar găsi că este gol, ceea ce nu este foarte util. Cu lista ascunsă de cititoarele de ecran, ei doar ar întâlni asta:

 <nav> </nav>

Iată cum am putea comuta starea:

 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-comenzi

După cum am scris în Aria-controls Is Poop, atributul aria-controls , menit să ajute utilizatorii cititorului de ecran să navigheze de la un element de control la un element controlat, este acceptat doar în cititorul de ecran JAWS. Deci pur și simplu nu te poți baza pe el.

Fără o metodă bună de direcționare a utilizatorilor între elemente, ar trebui să vă asigurați că una dintre următoarele este adevărată:

  1. Primul link al listei extinse este următorul în ordinea focalizării după buton (ca în exemplul de cod anterior).
  2. Prima legătură se concentrează programatic pe dezvăluirea listei.

În acest caz, aș recomanda (1). Este mult mai simplu, deoarece nu trebuie să vă faceți griji cu privire la mutarea focalizării înapoi la buton și asupra evenimentului (evenimentelor) să faceți acest lucru. De asemenea, momentan nu există nimic care să avertizeze utilizatorii că concentrarea lor va fi mutată într-un loc diferit. În meniurile adevărate pe care le vom discuta în curând, aceasta este treaba lui aria-haspopup="true" .

Utilizarea aria-controls nu face prea mult rău, cu excepția faptului că face citirea în cititoarele de ecran mai pronunțată. Cu toate acestea, unii utilizatori JAWS se pot aștepta la asta. Iată cum ar fi aplicat, folosind id -ul listei ca cifr:

 <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>

Rolurile meniului și ale elementelor de meniu

Un meniu adevărat (în sensul WAI-ARIA) ar trebui să se identifice ca atare folosind rolul de menu (pentru container) și, în mod obișnuit, copii ale elementelor de menuitem (se pot aplica și alte roluri de copil). Aceste roluri de părinte și copil lucrează împreună pentru a oferi informații tehnologiilor de asistență. Iată cum ar putea fi mărită o listă pentru a avea semantica meniului:

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

Deoarece meniul nostru de navigare începe să se comporte oarecum ca un meniu „adevărat”, acestea nu ar trebui să fie prezente?

Răspunsul scurt este: nu. Răspunsul lung este: nu, deoarece elementele noastre din listă conțin legături, iar elementele de menuitem nu sunt destinate să aibă descendenți interactivi. Adică sunt comenzile dintr-un meniu.

Am putea, desigur, să suprimăm semantica listei <li> folosind role="presentation" sau role="none" (care sunt echivalente) și să plasăm rolul elementului de menuitem pe fiecare link. Cu toate acestea, acest lucru ar suprima rolul implicit de legătură. Cu alte cuvinte, exemplul de urmat ar fi anunțat ca „Acasă, element de meniu”, nu „Acasă, link” sau „Acasă, element de meniu, link”. Rolurile ARIA înlocuiesc pur și simplu rolurile HTML.

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

Dorim ca utilizatorul să știe că folosește un link și că se poate aștepta la comportamentul link-ului, așa că nu este bun. După cum am spus, meniurile adevărate sunt pentru comportamentul aplicației (condus pe JavaScript).

Ceea ce ne rămâne este un fel de componentă hibridă, care nu este chiar un meniu adevărat, dar cel puțin le spune utilizatorilor dacă lista de link-uri este deschisă, datorită stării aria-expanded . Acesta este un model perfect satisfăcător pentru meniurile de navigare.

Notă laterală: elementul <select>

Dacă ați fost implicat în design responsive de la început, este posibil să vă amintiți un model prin care navigarea a fost condensată într-un element <select> pentru ferestre înguste.

receptor cu elementul de selectare care arată „acasă” selectat în partea de sus a ferestrei
Telefon cu elementul selectat care arată „acasă” selectat în partea de sus a ferestrei.

Ca și în cazul butoanelor de comutare bazate pe casete de selectare pe care le-am discutat, folosirea unui element nativ care se comportă oarecum așa cum este intenționat fără scriptare suplimentară este o alegere bună pentru eficiență și - mai ales pe mobil - performanță. Iar elementele <select> sunt un fel de meniuri, cu o semantică similară cu meniul declanșat de butoane pe care îl vom construi în curând.

Cu toate acestea, la fel ca în cazul butonului de comutare a casetei de selectare, folosim un element asociat cu introducerea intrării, nu pur și simplu să facem o alegere. Acest lucru este probabil să provoace confuzie pentru mulți utilizatori – mai ales că acest model folosește JavaScript pentru a face ca <option> selectată să se comporte ca un link. Schimbarea neașteptată a contextului pe care aceasta o provoacă este considerată un eșec conform criteriului WCAG 3.2.2 On Input (Nivel A).

Meniuri adevărate

Acum că am avut discuția despre meniuri și cvasi-meniuri false, a sosit momentul să creăm un meniu adevărat , așa cum este deschis și închis de un buton de meniu adevărat. De aici încolo, mă voi referi la buton și meniu împreună ca pur și simplu „buton de meniu”.

Dar în ce privințe va fi adevărat butonul nostru de meniu? Ei bine, va fi o componentă de meniu destinată alegerii opțiunilor din aplicația subiect, care implementează toată semantica așteptată și comportamentele corespunzătoare pentru a fi considerate convenționale pentru un astfel de instrument.

După cum sa menționat deja, aceste convenții provin din proiectarea aplicațiilor desktop. Atribuirea ARIA și gestionarea focalizării guvernate de JavaScript sunt necesare pentru a le imita pe deplin. O parte a scopului ARIA este de a ajuta dezvoltatorii web să creeze experiențe web bogate, fără a încălca convențiile de utilizare stabilite în lumea nativă.

În acest exemplu, ne vom imagina că aplicația noastră este un fel de joc sau test. Butonul nostru de meniu va permite utilizatorului să aleagă un nivel de dificultate. Cu toată semantica la loc, meniul arată astfel:

 <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>

Note

  • Proprietatea aria-haspopup indică pur și simplu că butonul secretă un meniu. Acționează ca un avertisment că, atunci când este apăsat, utilizatorul va fi mutat în meniul „pop-up” (vom acoperi comportamentul de focalizare în scurt timp). Valoarea sa nu se schimbă - rămâne la fel de true în orice moment.
  • <span> din interiorul butonului conține punctul unicode pentru un triunghi mic negru îndreptat în jos. Această convenție indică vizual ce face aria-haspopup non-vizual - că apăsarea butonului va dezvălui ceva dedesubt. Atribuirea aria-hidden="true" împiedică cititorii de ecran să anunțe „triunghi orientat în jos” sau similar. Datorită aria-haspopup , nu este necesar în context non-vizual.
  • Proprietatea aria-haspopup este completată de aria-expanded . Aceasta îi spune utilizatorului dacă meniul se află în prezent într-o stare deschisă (extinsă) sau închisă (restrâns) prin comutarea între valorile true și false .
  • Meniul în sine preia rolul de menu (numit corect). Este nevoie de descendenți cu rolul de menuitem de meniu. Nu trebuie să fie copii direcți ai elementului de menu , dar sunt în acest caz - pentru simplitate.

Comportamentul de la tastatură și de focalizare

Când vine vorba de a face tastatura de control interactiv accesibilă, cel mai bun lucru pe care îl poți face este să folosești elementele potrivite. Deoarece folosim elemente <button> aici, putem fi siguri că evenimentele de clic se vor declanșa la apăsarea tastelor Enter și Space , așa cum este specificat în interfața HTMLButtonElement. De asemenea, înseamnă că putem dezactiva elementele de meniu folosind proprietatea disabled asociată butoanelor.

Există, totuși, mult mai mult la interacțiunea cu tastatura butoanelor de meniu. Iată un rezumat al concentrării și comportamentului de la tastatură pe care îl vom implementa, pe baza practicilor de creație WAI-ARIA 1.1:

Introduceți , Spațiu sau pe butonul de meniu Deschide meniul
pe un element de meniu Mută ​​focalizarea la următorul element de meniu sau la primul element de meniu dacă sunteți la ultimul
pe un element de meniu Mută ​​focalizarea la elementul de meniu anterior sau ultimul element de meniu dacă sunteți la primul
pe butonul de meniu Închide meniul dacă este deschis
Esc pe un element de meniu Închide meniul și concentrează butonul de meniu

Avantajul de a muta focalizarea între elementele de meniu folosind tastele săgeți este că Tab este păstrat pentru a ieși din meniu. În practică, acest lucru înseamnă că utilizatorii nu trebuie să treacă prin fiecare element de meniu pentru a ieși din meniu - o îmbunătățire uriașă pentru utilizare, mai ales acolo unde există multe elemente de meniu.

Aplicarea tabindex="-1" face ca elementele de meniu să nu fie focalizate de Tab , dar păstrează capacitatea de a focaliza elementele în mod programatic, la capturarea tastelor de pe tastele săgeți.

 <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>

Metoda deschisă

Ca parte a unui design API solid, putem construi metode pentru gestionarea diferitelor evenimente.

De exemplu, metoda open trebuie să schimbe valoarea aria-expanded la „adevărat”, să schimbe proprietatea ascunsă a meniului la false și să focalizeze primul menuitem din meniu care nu este dezactivat:

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

Putem executa această metodă în care utilizatorul apasă tasta jos pe o instanță de buton de meniu focalizat:

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

În plus, un dezvoltator care utilizează acest script va putea acum să deschidă meniul în mod programatic:

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

Notă secundară: Hack-ul casetei de selectare

Pe cât posibil, este mai bine să nu utilizați JavaScript decât dacă este necesar. Implicarea unei a treia tehnologii pe lângă HTML și CSS este în mod necesar o creștere a complexității și fragilității sistemice. Cu toate acestea, nu toate componentele pot fi construite în mod satisfăcător fără JavaScript în amestec.

În cazul butoanelor de meniu, entuziasmul de a le face să „funcționeze fără JavaScript” a dus la ceva numit hack-ul casetei de selectare. Aici se utilizează starea bifată (sau debifată) a unei casete de selectare ascunse pentru a comuta vizibilitatea unui element de meniu folosind CSS.

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

Pentru utilizatorii cititorului de ecran, rolul casetei de selectare și starea bifată sunt lipsite de sens în acest context. Acest lucru poate fi depășit parțial adăugând role="button" la caseta de selectare.

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

Din păcate, acest lucru suprimă comunicarea implicită a stării verificate, privându-ne de feedback de stare fără JavaScript (deși slab ar fi fost la fel de „verificat” în acest context).

Dar este posibil să falsificați aria-expanded . Trebuie doar să furnizăm eticheta noastră cu două intervale, ca mai jos.

 <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>

Acestea sunt ambele ascunse vizual folosind clasa visually-hidden , dar, în funcție de starea în care ne aflăm, doar una este ascunsă și cititorilor de ecran. Adică, doar unul are display: none , iar acest lucru este determinat de starea verificată existentă (dar necomunicată):

 /* 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; }

Acest lucru este inteligent și totul, dar butonul nostru de meniu este încă incomplet, deoarece comportamentele de focalizare așteptate pe care le-am discutat pur și simplu nu pot fi implementate fără JavaScript.

Aceste comportamente sunt convenționale și așteptate, făcând butonul mai utilizabil. Cu toate acestea, dacă într-adevăr trebuie să implementați un buton de meniu fără JavaScript, acesta este cât de aproape puteți. Având în vedere că butonul de meniu de navigare pe care l-am acoperit anterior oferă conținut de meniu care nu este dependent de JavaScript în sine (adică link-uri), această abordare poate fi o opțiune potrivită.

Pentru distracție, iată un codePen care implementează un buton de meniu de navigare fără JavaScript.

Vedeți exemplul butonului de meniu Navigare stilou fără JS de Heydon (@heydon) pe CodePen.

( Notă: Doar Space deschide meniul.)

Evenimentul „alege”.

Executarea unor metode ar trebui să emită evenimente, astfel încât să putem configura ascultători. De exemplu, putem emite un eveniment de choose atunci când un utilizator face clic pe un element de meniu. Putem configura acest lucru folosind CustomEvent , care ne permite să transmitem un argument proprietății de detail a evenimentului. În acest caz, argumentul („alegerea”) ar fi nodul DOM al elementului de meniu ales.

 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; }

Sunt tot felul de lucruri pe care le putem face cu acest mecanism. Poate că avem o regiune live configurată cu un id de menuFeedback :

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

Acum putem configura un ascultător și populam regiunea live cu informațiile secretate în interiorul evenimentului:

 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}'; });
Când un utilizator alege o opțiune, meniul se închide și focalizarea este revenită la butonul de meniu. Este important ca utilizatorii să revină la elementul de declanșare după ce meniul este închis.
Când un utilizator alege o opțiune, meniul se închide și focalizarea este revenită la butonul de meniu. Este important ca utilizatorii să revină la elementul de declanșare după ce meniul este închis. (Previzualizare mare)

Când este selectat un element de meniu, utilizatorul cititorului de ecran va auzi „Ați ales [eticheta articolului de meniu]” . O regiune live (definită aici cu atribuirea role=“alert” ) își anunță conținutul în cititoarele de ecran ori de câte ori acel conținut se modifică. Regiunea live nu este obligatorie, dar este un exemplu a ceea ce s-ar putea întâmpla în interfață ca răspuns la alegerea de meniu de către utilizator.

Alegeri persistente

Nu toate elementele de meniu sunt pentru alegerea setărilor persistente. Multe acționează ca niște butoane standard care fac ca ceva în interfață să se întâmple atunci când sunt apăsate. Cu toate acestea, în cazul butonului nostru de meniu de dificultate, am dori să indicăm care este setarea curentă de dificultate - cea aleasă ultima.

Atributul aria-checked="true" funcționează pentru elementele care, în loc de menuitem , au rolul menuitemradio . Marcajul îmbunătățit, cu al doilea element bifat ( set ) arată astfel:

 <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>

Meniurile native de pe multe platforme indică elementele alese folosind bifă. Putem face asta fără probleme folosind puțin CSS suplimentar:

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

În timp ce parcurgeți meniul cu un cititor de ecran în funcțiune, focalizarea acestui element bifat va solicita un anunț precum „bifă, element de meniu mediu, bifat” .

Comportamentul la deschiderea unui meniu cu un menuitemradio bifat diferă ușor. În loc să focalizeze primul element (activat) din meniu, elementul bifat este focalizat.

Butonul de meniu începe cu meniul nedeschis. La deschidere, se concentrează a doua setare de dificultate (medie). Este prefixat cu o bifă bazată pe prezența atributului aria-verificat.
Butonul de meniu începe cu meniul nedeschis. La deschidere, se concentrează a doua setare de dificultate (medie). Este prefixat cu o bifă bazată pe prezența atributului aria-verificat. (Previzualizare mare)

Care este beneficiul acestui comportament? Utilizatorului (orice utilizator) i se reamintește opțiunea selectată anterior. În meniurile cu numeroase opțiuni incrementale (de exemplu, un set de niveluri de zoom), persoanele care operează prin tastatură sunt plasate în poziția optimă pentru a-și face reglajul.

Utilizarea butonului de meniu cu un cititor de ecran

În acest videoclip, vă voi arăta cum este să utilizați butonul de meniu cu cititorul de ecran Voiceover și Chrome. Exemplul folosește elemente cu menuitemradio , aria-checked și comportamentul de focalizare discutat. Experiențe similare pot fi așteptate în întreaga gamă de software populare de citire de ecran.

Butonul de meniu inclusiv pe Github

Kitty Giraudel și cu mine am lucrat împreună la crearea unei componente de buton de meniu cu funcțiile API pe care le-am descris și multe altele. Trebuie să-i mulțumești lui Hugo pentru multe dintre aceste funcții, deoarece s-au bazat pe munca pe care a făcut-o pe a11y-dialog - un dialog modal accesibil. Este disponibil pe Github și NPM.

 npm i inclusive-menu-button --save

În plus, Kitty a creat o versiune React pentru delectarea ta.

Lista de verificare

  • Nu utilizați semantica meniului ARIA în sistemele de meniuri de navigare.
  • Pe site-urile cu conținut bogat, nu ascundeți structura în meniurile de navigare imbricate bazate pe drop-down.
  • Utilizați aria-expanded pentru a indica starea deschis/închis a unui meniu de navigare activat de butoane.
  • Asigurați-vă că meniul de navigare menționat este următorul în ordinea focalizării după butonul care îl deschide/închide.
  • Nu sacrifica niciodată utilitatea în căutarea unor soluții fără JavaScript. E vanitate.