Napište si vlastní element select – na míru a přístupný
Správná specifikace jednotlivých prvků na stránce je pro přístupnost webu zásadní. Při vývoji se na ni ale často zapomíná, zvláště při tvorbě vlastních komponent. Na vzorovém projektu vám proto ukážeme, jak vytvořit vlastní element tak, aby odpovídal vašim potřebám a současně byl přístupný všem uživatelům.
Když vyvíjíte web, může se stát, že nativní prvky neodpovídají konkrétním potřebám. V takových případech je nejčastějším řešením návrh vlastní komponenty. Při honbě za perfektními vlastnostmi se ale mnohdy zapomíná na přístupnost.
V tomto článku se proto podíváme na to, co nám nativní prvky nabízí a jak vytvořit vlastní komponentu tak, aby ji mohli používat všichni uživatelé. Jako příklad nám poslouží element select. Než se do toho ale pustíme, vysvětlíme si, k čemu vůbec slouží a jak fungují přístupnostní rozhraní.
Jak funguje přístupností rozhraní
Součástí každé moderní aplikace je rozhraní určené k ovládání, které zároveň disponuje přístupnostním API. To na jedné straně dovoluje aplikacím popsat své rozhraní tak, aby byly přizpůsobitelné pro alternativní přístup a na druhé straně umožňuje asistivním technologiím (např. odečítačům obrazovky) tyto informace získávat a přizpůsobovat interakci potřebám uživatele.
V ideálním případě by stránka měla v kódu poskytovat informace ke každému svému prvku – jeho typ, stav a popis. Zvláště důležité je to u ovládacích prvků. Prohlížeč pak musí informacím uvedeným v kódu stránky rozumět a příslušným způsobem je propagovat do svého přístupnostního rozhraní.
Asistivní technologie přístupnostní rozhraní prohlížeče monitoruje a přebírá si od něj informace, které preferovaným způsobem zprostředkovává uživateli. Úspěšnost tohoto kroku závisí na tom, zda asistivní technologie umí využít maximum informací poskytovaných prohlížečem.
Máte přístupný web?
Nechte vaše stránky zkontrolovat a otestovat našimi odborníky. V rámci služby Analýza přístupnosti web zhodnotíme, navrhneme úpravy a poskytneme konzultace vašim vývojářům a webařům.
Existují případy, kdy informace o prvcích nelze vyjádřit prostředky HTML. Dnes už je mohou nahradit atributy přístupnosti, které jsou podporované napříč všemi webovými prohlížeči, a jsou obdobou přístupnostních vlastností v jiných prostředích – např. rozhraní NSAccessibillityProtocol v Apple App Kit. Přístupnostní atributy jsou ale na HTML nezávislé a ten, kdo nezná webové standardy přístupnosti, mnohdy neví, že v určitých případech má HTML kód o přístupnostní atributy obohatit.
Prvky rozhraní: na míru vs. ty nativní
V různých frameworcích existují předpřipravené komponenty, které již mají přístupnostní rozhraní implementováno. Jako příklad uveďme třeba Apple UIKit, v němž je k dispozici komponenta UITableView pro prezentaci seznamů položek. Její součástí je i přístupnostní rozhraní, takže asistivní odečítací funkce iOS VoiceOver může uživateli prvek správně zprostředkovat.
Kdybychom se rozhodli si postavit vlastní komponentu, takříkajíc na zelené louce, museli bychom přístupnostní rozhraní implementovat sami. Pokud tak neučiníme, asistivní technologie nebudou schopny uživateli čtení a ovládání takového seznamu zprostředkovat.
Pro výběrový seznam v HTML se používá element <select>. O jeho vykreslení, správnou identifikaci včetně vybrané položky a o ovládání, ať už myší nebo klávesnicí, se stará prohlížeč, potažmo operační systém. Uživatel jej ovládá obvyklým způsobem a vývojáři si jen musí v pravý čas zajistit odečtení vybrané položky.
V elementu <select> lze ovšem uvádět jen textové položky bez jakéhokoli formátování a možnosti přizpůsobení jeho designu proto nemusí pojetí webu vyhovovat. V tuto chvíli nezbývá nic jiného než si implementovat výběrový seznam vlastními silami.
3 hlavní zásady pro tvorbu vlastních komponent
- Nejdříve označte strukturu komponenty atributy WAI-ARIA, což vystaví její přístupnostní rozhraní.
- Potom napište obslužní skripty, které budou stav komponenty prezentovat nejen vizuální formou, ale právě i v jejím přístupnostním rozhraní.
- Další obslužné skripty by měly zajistit ovládání z klávesnice způsobem, který je pro typ dané komponenty obvyklý.
Vlastní komponenta výběrového seznamu
Pojďme si představit ukázkovou knihovnu pro tvorbu webových komponent, konkrétně její modul pro rozpohybování přístupných výběrových seznamů. Ukázkový projekt je dostupný v CodePen projektu.
Projekt se skládá z následujících souborů:
- controls.js – Knihovna poskytující rozhraní pro tvorbu komponent na základě předpřipravené HTML struktury.
- listbox.js – Rozšiřující modul pro rozpohybování výběrových seznamů. Příslušné funkci stačí předložit vyžadovanou HTML strukturu, eventuálně konfigurační objekt a tato knihovna se postará o zbytek – vytvoření a obsluhu seznamu.
- controls.css – Sada stylových předpisů, které zajišťují správné zobrazování výběrového seznamu a jeho stavů.
- listbox.html – Demo ukazující, jak pomocí přidružených knihoven je možno přístupný výběrový seznam rozpohybovat.
Podíváme-li se na zdrojový kód demonstrační stránky, zjistíme, že komponenta výběrového seznamu s CSS třídou ui-list_box neobsahuje nic neobvyklého. Má tři části:
- Element <input type=”hidden”> pro zrcadlení vybrané hodnoty v seznamu. To umožňuje formulář, který se na stránce nachází, zpracovávat obvyklým způsobem.
- Element <ul> pro prezentaci seznamu jako takového. Prozatím se jedná o obyčejný odrážkový seznam, který sám o sobě nic neumí. Příslušný vzhled mu dodají stylové předpisy a interakce pro změnu a přidružená knihovna s třídou ListBoxControl.
- Element <button> pro přepínání rozbalení seznamu, který zároveň plní úlohu indikátoru rozbalení.
Vytvoření komponenty
Z této statické struktury se stává dynamická až ve chvíli, kdy je níže na demonstrační stránce zavolána funkce createListBox(). Prvním parametrem této funkce je selektor určující elementy, jejichž obsah má být přetvořen na výběrové seznamy. V tomto případě je předán přímo element s ID ui-list_box. Dále by mohl být předán funkci objekt s konfigurací, v tuto chvíli však postačují výchozí hodnoty.
Objekt představovaný třídou komponenty ListBoxControl není vytvořen hned, ale až ve chvíli, kdy je celá stránka načtena. Z tohoto důvodu funkce createListBox() nevrací komponentu samotnou, ale objekt třídy Promise, v rámci jehož metod lze komponentu po vytvoření dále upravit. Na tomto místě je to využito k přiřazení popisku výběrovému seznamu skrze metodu ListBoxControl.setLabel().
Pohled na objekt konfigurace komponenty výběrového seznamu, který můžeme nepovinně předat funkci createListBox, potažmo konstruktoru třídy ListBoxControl, nám poodhalí něco z fungování komponenty. Máme k dispozici tyto volby (v závorce je vždy uvedena výchozí hodnota):
- id (= ‘ui-list_box’) – prefix pro ID elementů. Komponenta vyznačuje vztahy mezi různými elementy ve stránce (např. do příslušného přístupnostního atributu seznamu promítá aktuálně vybranou položku v něm) a k tomu potřebuje znát jejich ID. Pokud ID element nemá, je mu automaticky přiřazeno tak, aby bylo prefixováno uvedeným řetězcem.
- input (= ‘input’) – selektor formulářového políčka či element samotný, jehož hodnota je synchronizována s vybranou položkou v seznamu.
- default – hodnota položky, která má být v seznamu vybrána jako výchozí. Není-li tato možnost uvedena, považuje komponenta za výchozí tu hodnotu, která je uvedena v synchronizujícím se formulářovém políčku. Pokud ani to nemá výchozí hodnotu nebo určená hodnota neodpovídá žádné položce, je za výchozí považována první položka v seznamu.
- button (= ‘button’) – selektor tlačítka či element samotný, jenž slouží k přepínání rozbalení seznamu.
- list (= ‘ul’) – selektor elementu nebo element samotný, který má být považován za hlavní element výběrového seznamu. Ten bude získávat fokus a přijímat klávesové příkazy.
- options – selektor elementu nebo element samotný, jenž obsahuje položky seznamu: Položky musí být přímými potomky tohoto kontejneru. Pokud tato volba není uvedena, předpokládá se, že kontejnerem je element uvedený ve volbě list.
- Change (= ‘changeSelection’) – název události, která je vyvolána při změně výběru v seznamu a které je možno naslouchat v jiných skriptech.
- Expanded (= ‘expanded’) – CSS třída označující rozbalovací tlačítko, a to ve chvíli, kdy je seznam rozbalen. Zejména to lze využít ke změně zobrazeného symbolu na tlačítku.
- Selected (= ‘selected’) – touto CSS třídou je označena vždy ta položka seznamu, která je aktuálně vybrána.
- Current (= ‘current’) – označuje vždy tu položku seznamu, která je aktuálně zaměřena.
WAI-ARIA atributy
Po tom, co komponenta ztotožní všechny elementy potřebné ke svému fungování (volby konfigurace input, list, options a button), začne nad nimi budovat přístupnostní rozhraní. Doporučení konsorcia W3C WAI-ARIA (Accessible Rich Internet Applications) definuje sadu atributů, na základě nichž webový prohlížeč přístupnostní rozhraní iniciuje a během interakce se stránkou informuje asistivní technologie o důležitých událostech. Tyto atributy, ať už uvedené přímo v HTML kódu, nebo nastavené na elementech pomocí skriptu, přepisují výchozí hodnoty, které se element od elementu liší podle jejich účelu. V našem případě element <ul> má výchozí roli list, ale my ji změníme na listbox.
Komponenta označí elementy tvořící dohromady výběrový seznam následujícími atributy WAI-ARIA:
Element seznamu (konfigurační volba list):
- role=”listbox” – Určuje element jako výběrový seznam, který odpovídajícím způsobem asistivní technologie ohlásí.
- tabindex=”0″ – Určuje, že seznam bude dosažitelný při navigaci stránkou po ovládacích prvcích klávesou Tab (tabulátor).
- aria-setsize=”n” – Určuje počet položek v seznamu, kde n je jejich počet. Tento atribut je zde přítomen, neboť ve sbaleném stavu nejsou viditelné všechny položky a velikost seznamu by asistivní technologií mohla být špatně interpretována.
Elementy položek seznamu (potomci elementu daného konfigurační volbou options):
- role=”option” – Určuje, že má být element považován za položku seznamu.
- tabindex=”-1″ – Určuje, že položka seznamu je připravena programově získat fokus, což je potřeba v případě, kdy uživatel na položku klikne nebo se na ni přesune kurzorovými šipkami.
- aria-selected=”false” – Určuje, že položka seznamu je vybratelná, byť zatím nevybrána. Asistivní technologie na základě tohoto atributu oznamuje, zda je položka vybrána či nevybrána.
- aria-posinset=”i” – Určuje pozici položky v seznamu. Asistivní technologie je schopna oznámit, že se jedná o i-tou položku bez ohledu na to, zda pořadí souhlasí s aktuálním zobrazením.
Element rozbalovacího tlačítka (konfigurační volba button):
- aria-hidden=”true” – Určuje, že tento element má být z přístupnostního rozhraní vynechán. Toto tlačítko plní především roli vizuálního indikátoru rozbalení, zatímco přístupná informace o rozbalení seznamu je uvedena přímo u jeho hlavního elementu. I pro rozbalení seznamu ve skutečnosti není tlačítko potřeba, neboť stejný efekt má pro jistotu i kliknutí přímo na vybranou položku ve sbaleném stavu.
- tabindex=”-1″ – Explicitně určuje, že element má být vyjmut z navigace klávesou Tab (tabulátor). Jedná se o preventivní opatření pro případ, že tlačítko je nativní, tj. element <button>.
- aria-controls=”id” – Určuje ID elementu, který toto tlačítko ovládá. Je zde tedy uvedeno ID elementu seznamu. Vzhledem k tomu, že element je vyjmut z přístupnostního rozhraní, je tento atribut zde spíše pro formu.
Dále komponenta informuje asistivní technologie prostřednictvím atributů WAI-ARIA o stavu výběrového seznamu při následujících akcích:
- Rozbalení seznamu – Nastaví se atribut aria-expanded=”true” na elementu seznamu, čímž asistivní technologie seznam ohlásí jako rozbalený.
- Sbalení seznamu – Nastaví se atribut aria-expanded=”false” na elementu seznamu, čímž asistivní technologie seznam ohlásí jako sbalený.
- Změna vybrané položky – Nastaví atribut aria-selected=”true” na elementu položky, která je vybrána. Byla-li předtím vybrána položka jiná, nastaví se jí atribut aria-selected=”false”.
- Změna aktuální zaměřené položky – Nastaví se atribut aria-activedescendant=”id”, kde id je ID elementu položky, která má mít fokus. Výhoda tohoto řešení je, že webový prohlížeč udělí odkázané položce v tomto atributu fokus ve chvíli, kdy uživatel zaměří samotný výběrový seznam, např. přijde k němu klávesou Tab (tabulátor).
Události a ovládání
V tuto chvíli má výběrový seznam přístupnostní rozhraní takové, jaké má být. Přesto chybí jedna podstatná věc – nemůžeme jej ovládat. Proto musíme naslouchat událostem, které vyvolá uživatel a podle toho upravovat vnitřní stavy komponenty, přístupnostní atributy a stylové vlastnosti. V tomto případě už si nevystačíme s úpravami dokumentu stránky. Zde musí nastoupit opravdové programování v Javascriptu, resp. metody připravené ve třídě ListBoxControl.
Element seznamu:
- ‘blur’ (odejmutí fokusu) – Pokud uživatel odejde na stránce jinam (navigací po prvcích klávesou Tab (tabulátor) nebo klikne na jiný prvek stránky) a pokud je seznam rozbalený, sbalí se.
- ‘click’ (kliknutí nebo dotyk na prvku) – Pokud je seznam sbalený, rozbalí se. Pokud je již naopak rozbalený a k události dojde na některé z jeho položek, probublá událost k seznamu, kde se položka ztotožní, označí se za vybranou a seznam se sbalí.
- ‘keydown’ (stlačení klávesy) – Pokud je stisknuta některá z kláves nebo klávesových kombinací pro ovládání seznamu, provede se příslušná akce (viz dále). Často se zapomíná na krok, jejž i dobře napsané ovládání z klávesnice může zhatit. Je totiž nutné v případě klávesové kombinace určené pro ovládání seznamu zajistit, aby nebyla vykonána výchozí akce webovým prohlížečem (metoda Event.preventDefault()). Např. pokud by uživatel stiskl v seznamu ↓, tak na ni zareaguje a přejde na další položku seznamu. Jestliže ovšem nedojde k zabránění výchozí akce, doputuje událost až do okna prohlížeče, kde stisk této klávesy většinou znamená přesun na další řádek textu ve stránce, což může být zcela mimo seznam samotný. To v důsledku bohužel tedy znamená, že seznam ztratí fokus a sbalí se bez potvrzení výběru aktuální položky.
Element tlačítka:
- ‘click’ (kliknutí nebo dotyk na prvku) – Přepne rozbalení seznamu, kdy rozbalení zajistí i zaměření fokusu na seznam. Preventivně je i volána metoda Event.preventDefault() zabraňující webovému prohlížeči ve vykonání výchozí akce, kterou je v případě elementu <button> uvnitř formuláře jeho odeslání, což v této situaci je nežádoucí.
Ovládání z klávesnice je pro přístupnost klíčové nejen proto, že je svým způsobem univerzální a jednoduché co se týče řízení fokusu, ale i simulovatelné programově, např. asistivní technologií pro ovládání hlasem. V souladu s obvyklou praxí ovládání výběrových seznamů reaguje komponenta na tyto klávesové příkazy:
- ↑ – Přejde na předchozí položku v seznamu, přičemž pohyb je cyklický, tj. i z první na poslední.
- ↓ – Přejde na další položku v seznamu, pohyb je cyklický, tedy i z první na poslední.
- Alt + ↑ / Alt + ↓ – Je-li seznam sbalen, rozbalí ho a fokus zůstane na vybrané položce.
- Home / PageUp (Fn + ↑) – Přejde na první položku seznamu.
- End / PageDown (Fn + ↓) – Přejde na poslední položku seznamu.
- Enter (alternativně mezerník) – Je-li seznam rozbalen, prohlásí aktuální položku za vybranou a seznam sbalí.
- Esc – Je-li seznam rozbalen, sbalí jej bez změny vybrané položky.
- Alfanumerické znaky – Vyhledávají položky odpovídající napsaným znakům cyklicky ve směru vpřed. Vyhledané položky se uchovávají vždy na dobu 1 vteřiny, proto je možné vyhledávat i řetězec napsaný v jednom sledu.
Při ovládání z klávesnice vyniknou dvě vlastnosti, které při ovládání myší nebo dotykem patrné nejsou. Pokud je seznam sbalen, pak se při posuvu mezi jednotlivými položkami (třeba pomocí kurzorových šipek) aktuální označená položka automaticky změní na vybranou. U rozbaleného seznamu se při posunu po položkách aktuální položka stane vybranou až ve chvíli, kdy ji potvrdíme enterem.
Rozhraní seznamu
Je zvykem, že skrze Javascript každý prvek stránky poskytuje nějaké API rozhraní, skrze které jej můžeme ovládat a zkoumat jeho stav. Měla by ho tedy poskytovat i každá komponenta stvořená na zelené louce. Toto API sice nemá vliv na přístupnost, ale mělo by patřit k dobrým zvyklostem, které zvyšují interoperabilitu mezi komponentami stránky.
Vzorová komponenta představovaná v tomto případě třídou ListBoxControl poskytuje následující vlastnosti a metody typické pro výběrový seznam:
Vlastnosti pro čtení:
- currentIndex – Obsahuje pořadí (index číslovaný od 0) aktuálně zaměřené položky v kontejneru položek nebo null, jestliže je seznam prázdný.
- defaultValue – Obsahuje výchozí hodnotu určenou konfigurační volbou default, popř. hodnotu synchronizovaného formulářového políčka nebo 0 (odpovídá pořadí první položky).
- empty – Je true, pokud je seznam prázdný.
- expanded – Je true, pokud je seznam právě rozbalený.
- firstOption – Obsahuje element první položky v seznamu nebo null, jestliže je seznam prázdný.
- Inactive – Je true, pokud všechny vyžadované elementy výběrového seznamu nebyly úspěšně ztotožněny.
- lastOption – Obsahuje element poslední položky v seznamu nebo null, jestliže je seznam prázdný.
- selectedIndex – Obsahuje pořadí (index číslovaný od 0) vybrané položky v kontejneru položek nebo null, jestliže je seznam prázdný.
- size – Obsahuje počet položek v seznamu, tedy počet potomků v kontejneru položek.
Vlastnosti pro čtení a zápis:
- currentOption – Při čtení obsahuje element aktuálně zaměřené položky. Při přiřazení elementu do této vlastnosti se ověří, zda se jedná o položku seznamu a provedou se potřebné změny v přístupnostních atributech a stylových vlastnostech.
- selectedOption – Při čtení obsahuje element vybrané položky. Při přiřazení elementu do této vlastnosti se ověří, zda se jedná o položku seznamu a provedou se potřebné změny v přístupnostních atributech a stylových vlastnostech.
Metody pro získávání položek:
- addOption(element, value = null, before = null) – Vloží do kontejneru položek nový element, který označí příslušnými atributy. Vloží jej buď na konec kontejneru nebo před položku danou parametrem before. Je-li vyplněn parametr value, nastaví se hodnota položky do datového atributu value (data-value).
- getIndex(option) – Je-li element daný parametrem option některou z položek v seznamu, vrátí jeho pořadí v kontejneru, jinak null.
- getOptionAt(index) – Vrátí element položky dle daného pořadí v kontejneru položek, pokud takové pořadí existuje, jinak null. Index -1 vrátí poslední položku.
- getOptionBy(value) – Vrátí element položky seznamu, jehož hodnota (datový atribut value či alternativně jeho pořadí) odpovídá parametru value, pokud taková existuje, jinak null.
- getOptionTo(step, cycle = true) – Vrátí element položky, která je vzdálená o tolik kroků od aktuálně zaměřené položky, kolik udává parametr step. Přesahuje-li počet kroků hranice seznamu, zajišťuje parametr cycle ve výchozím stavu cyklický průchod, tedy po poslední položce následuje první a naopak. V případě parametru cycle s hodnotou false, tato metoda vrací null mimo hranice seznamu.
- getValue(option) – Vrací pro daný element položky její hodnotu (datový atribut value či alternativně jeho pořadí).
- removeOption(option) – Odstraní z kontejneru položek položku danou jejím elementem.
Metody pro ovládání seznamu:
- collapse(select = false) – Sbalí seznam a pokud select je true, nastaví se aktuálně zaměřená položka jako vybraná.
- expand() – Rozbalí seznam.
- follow(option) – Nastaví aktuálně zaměřenou položku podle parametru option a pokud je seznam sbalený, nastaví tuto položku též jako vybranou.
- jump(str, relative = true) – Vyhledává v textu položek zadaný řetězec a to buď od začátku, je-li relative = false, nebo od položky, která následuje za položkou aktuálně zaměřenou, je-li relative = true. Hledání skončí, najde-li se vyhovující položka, nebo se v kruhu dojde k položce, na níž hledání začalo. Při úspěšném hledání se položka zaměří. Navíc pokud tato metoda je volána vícekrát za sebou v intervalu menším než 1 vteřina, přidává se řetězec v parametru str k těm předchozím, aby bylo možné využít vyhledávání takříkajíc za letu.
Pokud splníte všechny výše zmíněné kroky, měla by být webová komponenta přístupná pro všechny uživatele bez rozdílu. Je ale třeba počítat s tím, že i když uděláme pro přístupnost maximum, stále mohou existovat rozdíly mezi naší komponentou a tou nativní, která nám nevyhovuje. Např. odečítači obrazovky Apple VoiceOver dělá někdy problém sledovat změny atributu aria-activedescendant, což vede k situaci, kdy se uživatel sice pohybuje po položkách v seznamu, ale ty přitom nejsou čteny.
Z tohoto důvodu bychom měli primárně hledat způsoby, jak přizpůsobit nativní ovládací prvky našim potřebám. V případě výběrového seznamu je tímto nativním prvkem element <select>, jenž lze přizpůsobit pomocí různých triků, jak ukazuje např. toto diskuzní vlákno. Grafická omezení tohoto prvku jsou řešitelná, narazit však můžeme na funkční omezení, která vyřešit nelze, např. položky jen v prostém textu.
Přiznáváme, že zde prezentovaný vzorový výběrový seznam má několik nedostatků – neumožňuje vícenásobný výběr, nehodí se pro větší množinu položek, u nichž by bylo nutné zajistit plynulý posun v rámci povolené velikosti seznamu, a jeho chování neodpovídá mobilnímu zobrazení.
4 tipy na závěr: na co se zaměřit při tvorbě přístupných komponent
- Využívejte nativní HTML prvky všude tam, kde je to možné. Výchozí grafické styly by neměly být důvodem pro jejich zavržení. Prezentujte těm, kteří o použitých komponentách rozhodují, výhody nativních prvků.
- Pokud musíte použít webové komponenty třetích stran, přesvědčte se, že jsou ovladatelné z klávesnice nebo ještě lépe, že mají přístupnostní rozhraní.
- Pokud si programujete vlastní webové komponenty, inspirujte se např. ve vzorových komponentách v rámci podpůrných materiálů WAI-ARIA. Najdete tam nejen seznam rolí a stavů potřebných pro přístupnostní rozhraní dané komponenty, ale i schéma doporučeného ovládání z klávesnice.
- Pečlivě uvažujte nad tím, na kterých elementech stránky budete naslouchat událostem přicházejícím od uživatele a buďte si vědomi toho, jak se události ve stránce distribuují (tzv. bubbling vs. capturing). Pokud hrozí kolize s jinými komponentami stránky či s příkazy webového prohlížeče, nezapomeňte použít metody Event.preventDefault() a Event.stopPropagation(). Ověřte si ale, zda je vyvolaná událost určena vaší komponentě.