Budowanie dostępnych systemów menu
Opublikowany: 2022-03-10Uwaga 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.
Zacznijmy od quizu. Czy pole linków zwisające z paska nawigacyjnego na ilustracji jest menu?
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:
- Menu ARIA nie są przeznaczone do nawigacji, ale do zachowania aplikacji. Wyobraź sobie system menu dla aplikacji komputerowej.
- 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.
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:
- Zapobiegaj domyślnemu zachowaniu łączy najwyższego poziomu (
e.preventDefault()
) i skryptowi w pełnej semantyce i zachowaniu menu WAI-ARIA. - 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.
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.
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:
- Identyfikuje się jako przycisk, a nie link;
- 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:
- Pierwszy link rozwiniętej listy jest następny w kolejności fokusu po przycisku (jak w poprzednim przykładzie kodu).
- 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 są 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.
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>
są 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">▾</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, coaria-haspopup
robi niewizualnie — że naciśnięcie przycisku pokaże coś poniżej. Atrybucjaaria-hidden="true"
uniemożliwia czytnikom ekranu ogłaszanie „trójkąta skierowanego w dół” lub podobnych. Dziękiaria-haspopup
nie jest to potrzebne w kontekście niewizualnym. - Właściwość
aria-haspopup
uzupełniaaria-expanded
. Informuje to użytkownika, czy menu jest aktualnie otwarte (rozwinięte), czy zamknięte (zwinięte), poprzez przełączanie między wartościamitrue
ifalse
. - Samo menu przejmuje (trafnie nazwaną) rolę
menu
. Przyjmuje potomków z roląmenuitem
. Nie muszą być bezpośrednimi dziećmi elementumenu
, 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">▾</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</span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">▾</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}'; });
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">▾</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.
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ść.