Budowanie dostępnych systemów menu

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ W sieci istnieje wiele różnych rodzajów menu. Tworzenie inkluzywnych doświadczeń to kwestia używania właściwych wzorców menu we właściwych miejscach, z właściwymi znacznikami i zachowaniem.

Uwaga redakcji : ten artykuł pierwotnie ukazał się na stronie Komponenty dołączane. Jeśli chcesz dowiedzieć się więcej o podobnych artykułach zawierających składniki, śledź @inclusicomps na Twitterze lub zasubskrybuj kanał RSS. Wspierając inclusive-components.design w Patreon, możesz pomóc w uczynieniu go najbardziej wszechstronną dostępną bazą danych niezawodnych komponentów interfejsu.

Klasyfikacja jest trudna. Weźmy na przykład kraby. Kraby pustelniki, kraby porcelanowe i kraby podkowiaste nie są — mówiąc taksonomicznie — prawdziwymi krabami. Ale to nie powstrzymuje nas przed użyciem przyrostka „krab”. Staje się bardziej zagmatwana, gdy z biegiem czasu i dzięki procesowi zwanemu karcynizacją , nieprawdziwe kraby ewoluują, aby bardziej przypominać prawdziwe kraby. Tak jest w przypadku krabów królewskich, które uważa się za pustelniki w przeszłości. Wyobraź sobie rozmiar ich muszli!

W projektowaniu często popełniamy ten sam błąd, nadając różnym rzeczom tę samą nazwę. Wyglądają podobnie, ale pozory mogą mylić. Może to mieć niefortunny wpływ na przejrzystość biblioteki komponentów. Jeśli chodzi o włączenie, może to również prowadzić do zmiany przeznaczenia nieodpowiedniego semantycznie i behawioralnie elementu. Użytkownicy będą oczekiwać jednego, a dostaną coś innego.

Termin „rozwijane” to klasyczny przykład. Wiele rzeczy "rozwija się" w interfejsach, w tym zestaw <option> z elementu <select> i ujawniona przez JavaScript lista linków, które tworzą podmenu nawigacji. Takie samo imię; zupełnie inne rzeczy. (Niektórzy oczywiście nazywają to „wyciąganiem”, ale nie zagłębiajmy się w to.)

Listy rozwijane, które stanowią zestaw opcji, są często nazywane „menu” i chcę o nich tutaj omówić. Będziemy opracowywać prawdziwe menu, ale po drodze jest wiele do powiedzenia na temat nieprawdziwych menu.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Zacznijmy od quizu. Czy pole linków zwisające z paska nawigacyjnego na ilustracji jest menu?

Pasek nawigacyjny zawiera link do sklepu, pod którym wisi zestaw trzech dalszych linków do strojów psa, gofrownic i magicznych kul.
Pasek nawigacyjny zawiera link do sklepu, pod którym wisi zestaw trzech dalszych linków do strojów psa, gofrownic i magicznych kul. (duży podgląd)

Odpowiedź brzmi nie, nie jest to prawdziwe menu.

Od dawna obowiązuje konwencja, że ​​schematy nawigacji składają się z list łączy. Konwencja prawie tak długo, jak mówi, że podnawigacja powinna być dostarczana jako zagnieżdżone listy łączy. Gdybym miał usunąć CSS dla komponentu zilustrowanego powyżej, powinienem zobaczyć coś takiego jak poniżej, z wyjątkiem koloru niebieskiego i Times New Roman.

Semantycznie rzecz biorąc, zagnieżdżone listy linków są w tym kontekście poprawne. Systemy nawigacyjne to tak naprawdę tabele treści i tak właśnie skonstruowane są tabele treści. Jedyną rzeczą, która naprawdę każe nam myśleć „menu”, jest stylizacja list zagnieżdżonych i sposób, w jaki są one ujawniane po najechaniu kursorem lub fokusie.

W tym miejscu niektórzy się mylą i zaczynają dodawać semantykę WAI-ARIA: aria-haspopup="true" , role="menu" , role="menuitem" itp. Jest na nie miejsce, jak omówimy, ale nie tutaj . Oto dwa powody, dla których:

  1. Menu ARIA nie są przeznaczone do nawigacji, ale do zachowania aplikacji. Wyobraź sobie system menu dla aplikacji komputerowej.
  2. Link najwyższego poziomu powinien być używany jako link , co oznacza, że ​​nie zachowuje się jak przycisk menu.

Odnośnie (2): Podczas przemierzania obszaru nawigacji z podmenu można oczekiwać, że każde podmenu pojawi się po najechaniu na łącze „najwyższego poziomu” („Sklep” na ilustracji). Spowoduje to zarówno odsłonięcie podmenu, jak i umieszczenie własnych linków w kolejności fokusu. Z niewielką pomocą JavaScript przechwytującego zdarzenia skupienia i rozmycia, aby zachować wygląd podmenu, gdy jest to potrzebne, osoba korzystająca z klawiatury powinna być w stanie po kolei przechodzić przez każdy link każdego poziomu.

Przyciski menu, które przyjmują właściwość aria-haspopup="true" nie zachowują się w ten sposób. Są one aktywowane kliknięciem i nie mają innego celu niż ujawnienie ukrytego menu.

ebook zniżki bieten wir
Po lewej: przycisk menu oznaczony „menu” z ikoną strzałki skierowanej w dół i aria-expanded = false. Po prawej: ten sam przycisk menu, ale z otwartym menu. Ten przycisk jest w stanie aria-expanded = true. (duży podgląd)

Jak na zdjęciu, czy to menu jest otwarte czy zamknięte, powinno być komunikowane za pomocą aria-expanded . Powinieneś zmienić ten stan tylko na kliknięcie, a nie na fokus. Użytkownicy zwykle nie oczekują wyraźnej zmiany stanu po samym zdarzeniu skupienia. W naszym systemie nawigacyjnym stan tak naprawdę się nie zmienia; to tylko sztuczka stylizacyjna. Behawioralnie możemy przechodzić przez nawigację tabulatorem tak, jakby nie miały miejsca żadne takie sztuczki typu „pokaż/ukryj”.

Problem z podmenu nawigacji

Podmenu nawigacyjne (lub niektóre z nich) działają dobrze z myszą lub klawiaturą, ale nie są tak gorące, jeśli chodzi o dotyk. Kiedy po raz pierwszy naciśniesz link „Sklep” najwyższego poziomu w naszym przykładzie, każesz mu otworzyć podmenu i podążać za linkiem.

Istnieją tutaj dwie możliwe rozwiązania:

  1. Zapobiegaj domyślnemu zachowaniu łączy najwyższego poziomu ( e.preventDefault() ) i skryptowi w pełnej semantyce i zachowaniu menu WAI-ARIA.
  2. Upewnij się, że każda strona docelowa najwyższego poziomu zawiera spis treści jako alternatywę dla podmenu.

(1) jest niezadowalająca, ponieważ, jak zauważyłem wcześniej, tego rodzaju semantyki i zachowania nie są oczekiwane w tym kontekście, w którym linki są kontrolkami tematu. Dodatkowo użytkownicy nie mogli już przejść do strony najwyższego poziomu, jeśli taka istnieje.

Uwaga dodatkowa: które urządzenia są urządzeniami dotykowymi?

Kuszące jest myślenie „to nie jest świetne rozwiązanie, ale dodam je tylko dla interfejsów dotykowych”. Problem polega na tym: jak wykryć, czy urządzenie ma ekran dotykowy?

Z pewnością nie powinieneś utożsamiać „małego ekranu” z „aktywowanym dotykiem”. Pracując w tym samym biurze, co ludzie zajmujący się produkcją ekranów dotykowych dla muzeów, mogę zapewnić, że niektóre z największych ekranów to ekrany dotykowe. Laptopy z dwiema klawiaturami i wejściem dotykowym również stają się coraz bardziej wydajne.

Z tego samego powodu wiele, ale nie wszystkie mniejsze urządzenia to urządzenia dotykowe. W projektowaniu inkluzywnym nie możesz sobie pozwolić na przyjmowanie założeń.

Rezolucja (2) jest bardziej inkluzywna i solidna, ponieważ zapewnia „rezerwację” dla użytkowników wszystkich danych wejściowych. Ale przerażające cytaty wokół terminu zastępczego są tutaj dość celowe, ponieważ w rzeczywistości uważam, że tabele treści na stronie są lepszym sposobem zapewniania nawigacji.

Wygląda na to, że nagrodzony zespół ds. rządowych usług cyfrowych się z tym zgadza. Być może widzieliście je również na Wikipedii.

Tabele treści Gov.uk są minimalne, a stylami list są myślniki. Wikipedia udostępnia obramowane szare pole z ponumerowanymi elementami. Oba są oznaczone treścią.
Tabele treści Gov.uk są minimalne, a stylami list są myślniki. Wikipedia udostępnia obramowane szare pole z ponumerowanymi elementami. Oba są oznaczone zawartością.

Spis treści

Spisy treści służą do nawigacji dla powiązanych stron lub sekcji stron i powinny być semantycznie podobne do głównych obszarów nawigacji w witrynie, przy użyciu elementu <nav> , listy i mechanizmu etykietowania grup.

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

Uwagi

  • W tym przykładzie wyobrażamy sobie, że każda sekcja to osobna strona, tak jak w menu rozwijanym.
  • Ważne jest, aby każda z tych stron „Sklep” miała taką samą strukturę, a spis treści „Produkty” znajdował się w tym samym miejscu. Spójność wspiera zrozumienie.
  • Lista grupuje elementy i wymienia je w danych wyjściowych technologii wspomagających, takich jak syntetyczny głos czytnika ekranu.
  • <nav> jest rekursywnie oznaczany przez nagłówek za pomocą aria-labelledby . Oznacza to, że „nawigacja po produktach” będzie ogłaszana w większości czytników ekranu po wejściu do regionu za pomocą klawisza Tab . Oznacza to również, że „nawigacja po produktach” będzie wyszczególniona w interfejsach elementów czytnika ekranu, z których użytkownicy mogą bezpośrednio przechodzić do regionów.

Wszystko na jednej stronie

Jeszcze lepiej, jeśli możesz zmieścić wszystkie sekcje na jednej stronie bez zbyt długiego i uciążliwego przewijania. Wystarczy połączyć się z identyfikatorem skrótu każdej sekcji. Na przykład, href="#waffle-irons" powinno wskazywać na .

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

( Uwaga: niektóre przeglądarki nie potrafią w rzeczywistości wysyłać fokusu do połączonych fragmentów stron. Umieszczenie tabindex="-1" na docelowym fragmencie rozwiązuje ten problem.)

Tam, gdzie witryna ma dużo treści, starannie skonstruowana architektura informacji, wyrażona poprzez liberalne użycie „menu” spisów treści, jest nieskończenie lepsza niż niepewny i nieporęczny system rozwijany. Nie tylko łatwiej jest tworzyć responsywne i wymaga mniej kodu, ale sprawia, że ​​wszystko staje się jaśniejsze: tam, gdzie rozwijane systemy ukrywają strukturę, tabele treści ją odsłaniają.

Niektóre witryny, w tym gov.uk Government Digital Service, zawierają strony indeksowe (lub „tematyczne”), które są po prostu spisami treści. To tak potężna koncepcja, że ​​popularny generator stron statycznych Hugo domyślnie generuje takie strony.

Diagram w stylu drzewa genealogicznego ze stroną docelową tematu u góry z dwoma oddzielnymi odgałęzieniami strony. Każde z poszczególnych odgałęzień strony ma wiele odgałęzień sekcji strony
Diagram w stylu drzewa genealogicznego ze stroną docelową tematu u góry z dwoma oddzielnymi odgałęzieniami strony. Każde z poszczególnych odgałęzień strony ma wiele odgałęzień sekcji strony (duży podgląd)

Architektura informacji to duża część integracji. Źle zorganizowana witryna może być tak zgodna technicznie, jak chcesz, ale nadal zrazi wielu użytkowników — zwłaszcza tych z zaburzeniami poznawczymi lub tych, którym brakuje czasu.

Przyciski menu nawigacji

Skoro jesteśmy przy temacie fałszywych menu związanych z nawigacją, byłoby zaniedbaniem, gdybym nie mówił o przyciskach menu nawigacji. Prawie na pewno widziałeś je oznaczone trzyliniową ikoną „hamburgera” lub „navicon”.

Nawet przy zredukowanej architekturze informacji i tylko jednej warstwie łączy nawigacyjnych, miejsce na małych ekranach jest na wagę złota. Ukrycie nawigacji za przyciskiem oznacza, że ​​w widocznym obszarze jest więcej miejsca na główną zawartość.

Przycisk nawigacyjny jest najbardziej zbliżoną do prawdziwego przycisku menu. Ponieważ ma na celu przełączanie dostępności menu po kliknięciu, powinno:

  1. Identyfikuje się jako przycisk, a nie link;
  2. Zidentyfikuj rozwinięty lub zwinięty stan odpowiadającego mu menu (które, ściśle mówiąc, jest tylko listą linków).

Progresywne ulepszanie

Ale nie wyprzedzajmy siebie. Powinniśmy pamiętać o progresywnym ulepszaniu i zastanowić się, jak to działa bez JavaScript.

W nieulepszonym dokumencie HTML niewiele można zrobić z przyciskami (poza przyciskami przesyłania, ale nie jest to nawet ściśle związane z tym, co chcemy tutaj osiągnąć). Może zamiast tego powinniśmy zacząć od linku, który prowadzi nas do nawigacji?

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

Nie ma sensu posiadanie łącza, chyba że między łączem a nawigacją jest dużo treści. Ponieważ nawigacja w witrynie powinna prawie zawsze pojawiać się w górnej części kolejności źródłowej, nie ma takiej potrzeby. Tak naprawdę menu nawigacyjne w przypadku braku JavaScript powinno być po prostu… nawigacją.

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

Ulepszasz to, dodając przycisk w stanie początkowym i ukrywając nawigację (używając hidden atrybutu):

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

Niektóre starsze przeglądarki — wiesz, które — nie obsługują hidden , więc pamiętaj, aby umieścić następujące elementy w swoim CSS. Rozwiązuje problem, ponieważ display: none ma takiego samego wpływu na ukrywanie menu przed technologiami pomocniczymi i usuwanie linków z kolejności fokusów.

 [hidden] { display: none; }

Robienie wszystkiego, co w naszej mocy, aby wspierać starsze oprogramowanie, to oczywiście projekt inkluzywny. Niektórzy nie mogą lub nie chcą uaktualnić.

Umieszczenie

Wiele osób popełnia błąd, umieszczając przycisk poza regionem. Oznaczałoby to, że użytkownicy czytników ekranu, którzy przejdą do <nav> za pomocą skrótu, uznaliby go za pusty, co nie jest zbyt pomocne. Gdy lista jest ukryta przed czytnikami ekranu, po prostu napotkaliby to:

 <nav> </nav>

Oto jak możemy przełączać stan:

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

Jak pisałem w Aria-controls Is Poop, atrybut aria-controls , który ma pomóc użytkownikom czytników ekranu w przechodzeniu od elementu sterującego do elementu kontrolowanego, jest obsługiwany tylko w czytniku ekranu JAWS. Więc po prostu nie możesz na nim polegać.

Bez dobrej metody kierowania użytkowników między elementami, powinieneś zamiast tego upewnić się, że jedno z poniższych jest prawdziwe:

  1. Pierwszy link rozwiniętej listy jest następny w kolejności fokusu po przycisku (jak w poprzednim przykładzie kodu).
  2. Pierwszy link skupia się programowo na ujawnieniu listy.

W takim przypadku polecam (1). Jest to o wiele prostsze, ponieważ nie musisz się martwić o przeniesienie fokusu z powrotem na przycisk i na które zdarzenie (zdarzenia), aby to zrobić. Ponadto obecnie nie ma nic, co mogłoby ostrzegać użytkowników, że ich skupienie zostanie przeniesione w inne miejsce. W prawdziwych menu, które omówimy wkrótce, jest to zadanie aria-haspopup="true" .

Użycie aria-controls tak naprawdę nie wyrządza wiele szkody, poza tym, że czytanie w czytnikach ekranu jest bardziej szczegółowe. Jednak niektórzy użytkownicy JAWS mogą się tego spodziewać. Oto jak to zostałoby zastosowane, używając id listy jako szyfru:

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

Role w menu i elementach menu

Prawdziwe menu (w sensie WAI-ARIA) powinno identyfikować się jako takie, używając roli menu (dla kontenera) i zazwyczaj menuitem podrzędnych menu (mogą mieć zastosowanie inne role podrzędne). Te role rodzica i dziecka współpracują ze sobą, aby dostarczać informacje do technologii wspomagających. Oto jak można rozszerzyć listę o semantykę menu:

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

Skoro nasze menu nawigacyjne zaczyna zachowywać się trochę jak „prawdziwe” menu, czy nie powinny one być obecne?

Krótka odpowiedź brzmi: nie. Długa odpowiedź brzmi: nie, ponieważ nasza lista zawiera linki, a elementy menuitem nie mają mieć interaktywnych potomków. Oznacza to, że to elementy sterujące w menu.

Moglibyśmy oczywiście pominąć semantykę listy <li> używając role="presentation" lub role="none" (które są równoważne) i umieścić rolę menuitem na każdym łączu. To jednak zniosłoby niejawną rolę linku. Innymi słowy, przykład do naśladowania będzie ogłaszany jako „Strona główna, pozycja menu”, a nie „Strona główna, link” lub „Strona główna, pozycja menu, link”. Role ARIA po prostu zastępują role HTML.

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

Chcemy, aby użytkownik wiedział, że korzysta z linku i może oczekiwać zachowania linku, więc to nie jest dobre. Tak jak powiedziałem, prawdziwe menu służą do zachowania aplikacji (napędzanych przez JavaScript).

Pozostaje nam rodzaj komponentu hybrydowego, który nie jest do końca prawdziwym menu, ale przynajmniej mówi użytkownikom, czy lista linków jest otwarta, dzięki aria-expanded . Jest to całkowicie zadowalający wzór dla menu nawigacyjnych.

Uwaga dodatkowa: element <select>

Jeśli od początku zajmowałeś się projektowaniem responsywnym, być może pamiętasz wzorzec, w którym nawigacja została skondensowana w elemencie <select> dla wąskich okien widokowych.

słuchawka z zaznaczonym elementem pokazującym “dom” wybrany w górnej części okienka ekranu
Słuchawka z zaznaczonym elementem pokazującym „dom” wybrany w górnej części rzutni.

Podobnie jak w przypadku omówionych przez nas przycisków przełączania opartych na polach wyboru, użycie natywnego elementu, który zachowuje się nieco zgodnie z przeznaczeniem bez dodatkowego skryptowania, jest dobrym wyborem ze względu na wydajność i — zwłaszcza na urządzeniach mobilnych — wydajność. A elementy <select> swego rodzaju menu, z semantyką podobną do menu uruchamianego przyciskiem, które wkrótce będziemy konstruować.

Jednak, podobnie jak w przypadku przełącznika pola wyboru, używamy elementu związanego z wprowadzaniem danych wejściowych, a nie tylko dokonywaniem wyboru. Prawdopodobnie spowoduje to zamieszanie dla wielu użytkowników — zwłaszcza, że ​​ten wzorzec używa JavaScript, aby wybrana <option> zachowywała się jak link. Nieoczekiwana zmiana kontekstu, jaką wywołuje, jest uważana za porażkę zgodnie z kryterium WCAG 3.2.2 dotyczącym danych wejściowych (poziom A).

Prawdziwe menu

Teraz, gdy rozmawialiśmy o fałszywych menu i quasi-menu, nadszedł czas, aby stworzyć prawdziwe menu, otwierane i zamykane przez prawdziwy przycisk menu. Od tego momentu przycisk i menu będę odnosić się do przycisku i menu jako po prostu „przycisku menu”.

Ale pod jakimi względami nasz przycisk menu będzie prawdziwy? Cóż, będzie to składnik menu przeznaczony do wybierania opcji w aplikacji przedmiotowej, który implementuje całą oczekiwaną semantykę i odpowiadające jej zachowania, które należy uznać za konwencjonalne dla takiego narzędzia.

Jak już wspomniano, konwencje te pochodzą z projektowania aplikacji komputerowych. ARIA atrybucja i zarządzanie fokusem zarządzane przez JavaScript są potrzebne, aby w pełni je naśladować. Jednym z celów ARIA jest pomoc programistom internetowym w tworzeniu bogatych doświadczeń internetowych bez łamania konwencji użyteczności wykutych w rodzimym świecie.

W tym przykładzie wyobrazimy sobie, że nasza aplikacja jest rodzajem gry lub quizu. Nasz przycisk menu pozwoli użytkownikowi wybrać poziom trudności. Przy całej semantyce menu wygląda tak:

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

Uwagi

  • Właściwość aria-haspopup po prostu wskazuje, że przycisk otwiera menu. Działa jako ostrzeżenie, że po naciśnięciu użytkownik zostanie przeniesiony do menu „podręcznego” (wkrótce omówimy zachowanie fokusa). Jego wartość true nie zmienia — zawsze pozostaje taka sama.
  • <span> wewnątrz przycisku zawiera punkt Unicode dla czarnego małego trójkąta skierowanego w dół. Ta konwencja wskazuje wizualnie, co aria-haspopup robi niewizualnie — że naciśnięcie przycisku pokaże coś poniżej. Atrybucja aria-hidden="true" uniemożliwia czytnikom ekranu ogłaszanie „trójkąta skierowanego w dół” lub podobnych. Dzięki aria-haspopup nie jest to potrzebne w kontekście niewizualnym.
  • Właściwość aria-haspopup uzupełnia aria-expanded . Informuje to użytkownika, czy menu jest aktualnie otwarte (rozwinięte), czy zamknięte (zwinięte), poprzez przełączanie między wartościami true i false .
  • Samo menu przejmuje (trafnie nazwaną) rolę menu . Przyjmuje potomków z rolą menuitem . Nie muszą być bezpośrednimi dziećmi elementu menu , ale w tym przypadku są — dla uproszczenia.

Zachowanie klawiatury i ostrości

Jeśli chodzi o udostępnianie interaktywnej klawiatury sterującej, najlepszą rzeczą, jaką możesz zrobić, to użyć odpowiednich elementów. Ponieważ używamy tutaj elementów <button> , możemy być pewni, że zdarzenia kliknięcia będą uruchamiane po naciśnięciu klawisza Enter i spacji , jak określono w interfejsie HTMLButtonElement. Oznacza to również, że możemy wyłączyć elementy menu za pomocą powiązanej z przyciskiem właściwości disabled .

Jest jednak o wiele więcej interakcji z klawiaturą menu. Oto podsumowanie wszystkich działań związanych z fokusem i klawiaturą, które zamierzamy wdrożyć, w oparciu o praktyki autorskie WAI-ARIA 1.1:

Enter , spacja lub na przycisku menu Otwiera menu
na pozycji menu Przenosi fokus do następnego elementu menu lub pierwszego elementu menu, jeśli jesteś na ostatnim
na pozycji menu Przenosi fokus na poprzedni element menu lub ostatni element menu, jeśli jesteś na pierwszym
na przycisku menu Zamyka menu, jeśli jest otwarte
Esc w pozycji menu Zamyka menu i ustawia ostrość przycisku menu

Zaletą przenoszenia fokusu między pozycjami menu za pomocą klawiszy strzałek jest zachowanie klawisza Tab podczas wychodzenia z menu. W praktyce oznacza to, że użytkownicy nie muszą przechodzić przez każdą pozycję menu, aby wyjść z menu — ogromna poprawa użyteczności, zwłaszcza w przypadku wielu pozycji menu.

Zastosowanie tabindex="-1" sprawia, że ​​elementy menu nie są widoczne przez Tab , ale zachowuje możliwość programowego skupienia elementów, po przechwyceniu naciśnięć klawiszy na klawiszach strzałek.

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

W ramach projektowania dźwięku API możemy konstruować metody obsługi różnych zdarzeń.

Na przykład, metoda open musi przełączyć wartość aria-expanded na „true”, zmienić właściwość hidden menu na false i skoncentrować się na pierwszym menuitem menu w menu, który nie jest wyłączony:

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

Możemy wykonać tę metodę, w której użytkownik naciśnie klawisz strzałki w dół na instancji przycisku menu, na który się skupiono:

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

Ponadto programista korzystający z tego skryptu będzie mógł teraz programowo otworzyć menu:

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

Uwaga dodatkowa: hack pola wyboru

O ile to możliwe, lepiej nie używać JavaScript, chyba że jest to konieczne. Włączenie trzeciej technologii do HTML i CSS jest z konieczności zwiększeniem złożoności i kruchości systemu. Jednak nie wszystkie komponenty można w sposób zadowalający zbudować bez użycia JavaScript w miksie.

W przypadku przycisków menu entuzjazm, by „działały bez JavaScriptu”, doprowadził do czegoś, co nazywa się hackowaniem pola wyboru. W tym miejscu zaznaczony (lub niezaznaczony) stan ukrytego pola wyboru jest używany do przełączania widoczności elementu menu za pomocą CSS.

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

Dla użytkowników czytników ekranu rola pola wyboru i stan zaznaczenia są w tym kontekście bezsensowne. Można to częściowo obejść, dodając role="button" do pola wyboru.

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

Niestety, blokuje to niejawną komunikację w stanie zaznaczonym, pozbawiając nas informacji zwrotnej o stanie wolnym od JavaScriptu (kiepskie, choć w tym kontekście byłoby to tak samo „sprawdzone”).

Ale można sfałszować aria-expanded . Musimy tylko dostarczyć naszą etykietę z dwoma przęsłami, jak poniżej.

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

Oba są wizualnie ukryte za pomocą visually-hidden klasy, ale — w zależności od tego, w jakim stanie się znajdujemy — tylko jedna jest ukryta również dla czytników ekranu. Oznacza to, że tylko jeden ma display: none , a jest to określane przez istniejący (ale nie komunikowany) stan zaznaczenia:

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

To sprytne i wszystko, ale nasz przycisk menu jest wciąż niekompletny, ponieważ oczekiwane zachowania skupienia, o których mówiliśmy, po prostu nie mogą być zaimplementowane bez JavaScript.

Te zachowania są konwencjonalne i oczekiwane, dzięki czemu przycisk jest bardziej użyteczny. Jeśli jednak naprawdę potrzebujesz zaimplementować przycisk menu bez JavaScript, jest to tak blisko, jak to tylko możliwe. Biorąc pod uwagę, że skrócony przycisk menu nawigacyjnego, który omówiłem wcześniej, oferuje zawartość menu, która sama w sobie nie jest zależna od JavaScript (tj. linki), to podejście może być odpowiednią opcją.

Dla zabawy, oto codePen implementujący przycisk menu nawigacji wolny od JavaScript.

Zobacz przykład przycisku menu Pen Navigation no JS by Heydon (@heydon) w CodePen.

( Uwaga: tylko spacja otwiera menu.)

Wydarzenie „wybierz”

Wykonanie niektórych metod powinno emitować zdarzenia, abyśmy mogli skonfigurować detektory. Na przykład możemy wyemitować zdarzenie select, gdy choose kliknie element menu. Możemy to ustawić za pomocą CustomEvent , co pozwala nam przekazać argument do właściwości detail zdarzenia. W tym przypadku argumentem („wybór”) byłby węzeł DOM wybranej pozycji menu.

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

Jest wiele rzeczy, które możemy zrobić za pomocą tego mechanizmu. Być może mamy skonfigurowany region na żywo z id menuFeedback :

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

Teraz możemy skonfigurować słuchacza i wypełnić region na żywo informacjami ukrytymi w wydarzeniu:

 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}'; });
Gdy użytkownik wybierze opcję, menu zamyka się, a fokus powraca do przycisku menu. Ważne jest, aby użytkownicy wracali do elementu wyzwalającego po zamknięciu menu.
Gdy użytkownik wybierze opcję, menu zamyka się, a fokus powraca do przycisku menu. Ważne jest, aby użytkownicy wracali do elementu wyzwalającego po zamknięciu menu. (duży podgląd)

Po wybraniu elementu menu użytkownik czytnika ekranu usłyszy komunikat „Wybrałeś [etykieta elementu menu]” . Aktywny region (zdefiniowany tutaj z przypisaniem role=“alert” ) ogłasza swoją zawartość w czytnikach ekranu za każdym razem, gdy ta zawartość się zmieni. Region na żywo nie jest obowiązkowy, ale jest przykładem tego, co może się wydarzyć w interfejsie w odpowiedzi na wybór przez użytkownika w menu.

Trwałe wybory

Nie wszystkie pozycje menu służą do wybierania trwałych ustawień. Wiele z nich działa jak standardowe przyciski, które po naciśnięciu powodują, że coś się dzieje w interfejsie. Jednak w przypadku naszego przycisku menu trudności chcielibyśmy wskazać, które jest aktualnie ustawionym poziomem trudności — tym, który został wybrany jako ostatni.

Atrybut aria-checked="true" działa dla elementów, które zamiast menuitem przyjmują rolę menuitemradio . Ulepszony znacznik, z zaznaczoną drugą pozycją ( set ) wygląda tak:

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

Natywne menu na wielu platformach wskazują wybrane pozycje za pomocą znaczników wyboru. Możemy to zrobić bez problemu, używając trochę dodatkowego CSS:

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

Podczas przechodzenia przez menu z uruchomionym czytnikiem ekranu skupienie się na tym zaznaczonym elemencie spowoduje wyświetlenie komunikatu, takiego jak „znacznik wyboru, średnia pozycja menu, zaznaczona” .

Zachowanie podczas otwierania menu z zaznaczonym menuitemradio nieco się różni. Zamiast skupiać się na pierwszym (włączonym) elemencie menu, zamiast tego skupiany jest zaznaczony element.

Przycisk menu zaczyna się od nieotwartego menu. Na otwarciu drugiego (średniego) poziomu trudności koncentruje się. Jest poprzedzony znacznikiem wyboru opartym na obecności atrybutu aria-checked.
Przycisk menu zaczyna się od nieotwartego menu. Na otwarciu drugiego (średniego) poziomu trudności koncentruje się. Jest poprzedzony znacznikiem wyboru opartym na obecności atrybutu aria-checked. (duży podgląd)

Jaka jest korzyść z tego zachowania? Użytkownik (dowolny użytkownik) otrzymuje przypomnienie o wybranej wcześniej opcji. W menu z licznymi opcjami przyrostowymi (na przykład zestawem poziomów powiększenia) osoby obsługujące klawiaturę są ustawiane w optymalnej pozycji, aby dokonać ich regulacji.

Korzystanie z przycisku menu z czytnikiem ekranu

W tym filmie pokażę, jak to jest używać przycisku menu z czytnikiem ekranu Voiceover i Chrome. W przykładzie użyto elementów z menuitemradio , aria-checked i omówionym zachowaniem fokusa. Podobnych doświadczeń można się spodziewać w całej gamie popularnych programów do odczytu ekranu.

Włączny przycisk menu na Github

Kitty Giraudel i ja pracowaliśmy razem nad stworzeniem komponentu przycisku menu z opisanymi przeze mnie funkcjami API i nie tylko. Za wiele z tych funkcji należy podziękować Hugo, ponieważ opierały się one na pracy, jaką wykonali nad a11y-dialog — dostępnym oknem modalnym. Jest dostępny na Github i NPM.

 npm i inclusive-menu-button --save

Ponadto Kitty stworzyła wersję React dla Twojej rozkoszy.

Lista kontrolna

  • Nie używaj semantyki menu ARIA w systemach menu nawigacji.
  • W witrynach o dużej zawartości treści nie ukrywaj struktury w zagnieżdżonych menu nawigacyjnych z rozwijanym menu.
  • Użyj opcji aria-expanded , aby wskazać stan otwarcia/zamknięcia menu nawigacyjnego aktywowanego przyciskiem.
  • Upewnij się, że wspomniane menu nawigacyjne jest następne w kolejności fokusu po przycisku, który je otwiera/zamyka.
  • Nigdy nie rezygnuj z użyteczności w pogoni za rozwiązaniami wolnymi od JavaScriptu. To próżność.