ARIA może poprawić dostępność dynamicznych komponentów, ale źle użyta potrafi zepsuć interfejs bardziej niż jej brak. Najważniejsza zasada brzmi: najpierw semantyczny HTML, dopiero potem ARIA.
WAI-ARIA definiuje role, stany i właściwość, które pomagają technologiom wspomagającym rozumieć komponenty interfejsu, szczególnie w aplikacjach tworzonych z użyciem JavaScriptu. ARIA nie dodaje jednak działania, obsługi klawiatury ani poprawnego fokusu. To musi zapewnić kod aplikacji.
Dla kogo jest ten tutorial?
Dla osób, które projektują, kodują albo audytują interfejsy:
- deweloperów front-end,
- audytorów dostępności cyfrowej,
- UX/UI designerów,
- redaktorów technicznych,
- osób wdrażających WCAG w aplikacjach webowych.
Po przeczytaniu tej strony masz wiedzieć:
- co robią najważniejsze atrybuty ARIA,
- kiedy używać ARIA, a kiedy zwykłego HTML,
- jak testować ARIA z klawiaturą i czytnikiem ekranu,
- które błędy ARIA najczęściej psują dostępność.
Czym jest ARIA?
ARIA oznacza Accessible Rich Internet Applications. To zestaw ról i atrybutów, które można dodać do HTML, aby przekazać technologiom wspomagającym dodatkowe informacje o elemencie.
ARIA odpowiada między innymi na pytania:
- czym jest element: przyciskiem, dialogiem, alertem, zakładką, przełącznikiem,
- jaką ma nazwę dostępną,
- czy jest rozwinięty, zaznaczony, wybrany, błędny albo wyłączony,
- z jakim opisem lub komunikatem błędu jest powiązany,
- czy zmiana treści ma zostać ogłoszona przez czytnik ekranu.
ARIA działa na poziomie semantyki dostępności. To oznacza, że może zmienić sposób, w jaki element jest widoczny w drzewie dostępności, ale nie sprawi automatycznie, że element zacznie działać jak natywna kontrolka HTML.
Najważniejsza zasada: HTML przed ARIA
Jeżeli istnieje natywny element HTML, który robi to samo, użyj natywnego elementu.
Źle
<div role="button" tabindex="0" onclick="sendForm()">
Wyślij formularz
</div>Taki element tylko udaje przycisk. Trzeba samodzielnie dodać obsługę fokusu, aktywację spacją i Enterem, poprawną semantykę oraz przewidywalne zachowanie.
Dobrze
<button type="submit">
Wyślij formularz
</button>Natywny <button> ma od razu:
- rolę przycisku,
- obsługę klawiatury,
- obsługę fokusu,
- obsługę kliknięcia,
- poprawną komunikację z technologiami wspomagającymi.
ARIA a WCAG
ARIA najczęściej wpływa na te kryteria sukcesu WCAG:
| Kryterium | Znaczenie w kontekście ARIA |
|---|---|
| 1.3.1 Informacje i relacje | Relacje widoczne wizualnie muszą być możliwe do odczytania programowo. Dotyczy między innymi etykiet, instrukcji, komunikatów błędów, regionów i grup pól. |
| 2.1.1 Klawiatura | Jeżeli tworzysz komponent z ARIA, musi działać z klawiatury. |
| 2.4.3 Kolejność fokusu | Elementy interaktywne muszą mieć logiczną kolejność fokusu. |
| 2.4.7 Widoczny fokus | Użytkownik klawiatury musi widzieć, gdzie aktualnie jest fokus. |
| 2.5.3 Etykieta w nazwie | Widoczna etykieta powinna być częścią nazwy dostępnej. Szczególnie ważne przy aria-label. |
| 3.3.1 Identyfikacja błędu | Błędy muszą być wskazane tekstowo. ARIA może połączyć pole z komunikatem błędu. |
| 3.3.2 Etykiety lub instrukcje | Użytkownik musi wiedzieć, czego oczekuje formularz. ARIA nie zastępuje widocznej instrukcji. |
| 4.1.2 Nazwa, rola, wartość | Komponenty interfejsu muszą mieć programowo określoną nazwę, rolę, stan i wartość. |
| 4.1.3 Komunikaty o stanie | Komunikaty dynamiczne mogą być przekazywane przez regiony live bez przenoszenia fokusu. |
Trzy grupy ARIA
ARIA można praktycznie podzielić na trzy grupy.
| Grupa | Przykłady | Do czego służy |
|---|---|---|
| Role | role="button", role="dialog", role="alert", role="tab" | Mówią, czym jest element. |
| Stany | aria-expanded, aria-checked, aria-selected, aria-invalid | Mówią, w jakim stanie jest element. |
| Właściwości | aria-label, aria-labelledby, aria-describedby, aria-controls | Dodają nazwę, opis, relację albo dodatkową informację semantyczną. |
Zasada „rola jest obietnicą”
Jeżeli oznaczasz element jako przycisk, zakładkę, menu albo suwak, obiecujesz użytkownikowi technologii wspomagającej, że element będzie działał jak taki komponent.
To oznacza, że musisz zapewnić:
- poprawną obsługę klawiatury,
- przewidywalny fokus,
- poprawną nazwę dostępną,
- aktualny stan,
- zgodność zachowania z oczekiwanym wzorcem.
Nie wystarczy dopisać role="button" albo aria-expanded="true".
Słownik ARIA
Ten słownik pokazuje najczęściej używane atrybuty i role ARIA: co robią, kiedy ich używać i gdzie mogą powodować błędy. JavaScript dodaje filtrowanie, ale treść musi pozostać dostępna bez skryptów.
-
role="alert"rolaCo robi: Oznacza pilny komunikat, który technologie wspomagające mogą ogłosić natychmiast.
Kiedy używać: Dla krytycznych błędów i informacji wymagających szybkiej reakcji użytkownika.
Kiedy uważać: Nie używaj do zwykłych statusów, potwierdzeń ani informacji, które mogą poczekać.
Powiązane WCAG: 4.1.3
Ryzyko: Nadużywanie alertów przerywa użytkownikowi pracę i tworzy hałas informacyjny.
<p role="alert">Sesja za chwilę wygaśnie.</p> -
role="button"rolaCo robi: Informuje, że element ma być przyciskiem w drzewie dostępności.
Kiedy używać: Tylko gdy nie możesz użyć natywnego
<button>i odtwarzasz pełne zachowanie przycisku.Kiedy uważać: Nie używaj na
divlubspantylko dlatego, że element wygląda jak przycisk.Powiązane WCAG: 2.1.1, 2.4.7, 4.1.2
Ryzyko: Sama rola nie dodaje obsługi klawiatury, fokusu ani aktywacji spacją.
<button type="button">Zamknij</button> -
role="dialog"rolaCo robi: Oznacza okno dialogowe, które wymaga wyraźnego kontekstu i nazwy dostępnej.
Kiedy używać: Przy faktycznych dialogach lub modalach z poprawną obsługą fokusu.
Kiedy uważać: Nie używaj dla zwykłych sekcji strony ani paneli, które nie zachowują się jak dialog.
Powiązane WCAG: 2.1.1, 2.4.3, 4.1.2
Ryzyko: Dialog bez przeniesienia, zatrzymania i przywrócenia fokusu może być niedostępny z klawiatury.
<div role="dialog" aria-modal="true" aria-labelledby="dialog-title"> <h2 id="dialog-title">Potwierdź usunięcie</h2> </div> -
role="status"rolaCo robi: Tworzy niepilny komunikat o stanie, ogłaszany bez przenoszenia fokusu.
Kiedy używać: Dla wyników filtrowania, zapisu, postępu, potwierdzeń i podobnych statusów.
Kiedy uważać: Nie używaj dla komunikatów krytycznych, które wymagają natychmiastowej reakcji.
Powiązane WCAG: 4.1.3
Ryzyko: Zbyt częste aktualizacje statusu mogą przeszkadzać w pracy użytkownika.
<p role="status">Pokazano 7 wyników.</p> -
role="switch"rolaCo robi: Oznacza przełącznik z dwoma stanami: włączony albo wyłączony.
Kiedy używać: Gdy kontrolka faktycznie działa jak przełącznik i nie możesz użyć natywnego checkboxa.
Kiedy uważać: Nie używaj do zwykłych przycisków akcji ani do rozwijania sekcji.
Powiązane WCAG: 2.1.1, 4.1.2
Ryzyko: Stan
aria-checkedmusi być aktualizowany razem ze stanem wizualnym.<button type="button" role="switch" aria-checked="false"> Powiadomienia </button> -
role="tab"rolaCo robi: Oznacza zakładkę w komponencie kart, zwykle razem z
tablistitabpanel.Kiedy używać: Przy prawdziwych zakładkach, które przełączają widoczne panele w tym samym widoku.
Kiedy uważać: Nie używaj do zwykłej nawigacji między stronami. Tam wystarczą linki i
aria-current.Powiązane WCAG: 2.1.1, 2.4.3, 4.1.2
Ryzyko: Zakładki wymagają obsługi klawiatury,
aria-selectedi poprawnych relacji z panelami.<div role="tablist" aria-label="Sekcje"> <button role="tab" aria-selected="true" aria-controls="panel-a">A</button> </div> -
aria-activedescendantwłaściwośćCo robi: Wskazuje aktualnie aktywny element potomny, gdy fokus pozostaje na elemencie nadrzędnym.
Kiedy używać: W złożonych komponentach, takich jak combobox, listbox, grid albo tree, gdy faktyczny fokus DOM pozostaje na kontenerze lub polu wejściowym.
Kiedy uważać: Nie używaj w prostych listach linków albo wtedy, gdy możesz przenosić prawdziwy fokus na aktywny element.
Powiązane WCAG: 1.3.1, 2.1.1, 4.1.2
Ryzyko: Jeżeli wskazane id nie istnieje albo aktywny element nie jest logicznym potomkiem komponentu, czytnik ekranu może ogłaszać błędny stan.
<input role="combobox" aria-activedescendant="option-2" aria-controls="results"> <ul id="results" role="listbox"> <li id="option-2" role="option">Warszawa</li> </ul> -
aria-atomicwłaściwośćCo robi: Określa, czy przy zmianie regionu live ma zostać ogłoszony cały region, czy tylko zmieniona część.
Kiedy używać: Gdy zmieniona część bez kontekstu jest niezrozumiała, np. sama liczba w komunikacie koszyka.
Kiedy uważać: Nie stosuj na dużych sekcjach, bo drobna zmiana może powodować odczytywanie zbyt dużej ilości treści.
Powiązane WCAG: 4.1.3
Ryzyko: Zbyt duży region z aria-atomic=true może zasypywać użytkownika powtarzanymi komunikatami.
<p aria-live="polite" aria-atomic="true">W koszyku: <span>3</span> produkty</p> -
aria-autocompletewłaściwośćCo robi: Informuje, jaki typ sugestii automatycznego uzupełniania jest dostępny.
Kiedy używać: Przy comboboxach i polach z sugestiami, gdy użytkownik może wybierać albo akceptować podpowiedzi.
Kiedy uważać: Nie stosuj przy zwykłym polu tekstowym bez faktycznego mechanizmu sugestii.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Fałszywe aria-autocomplete sugeruje funkcję, której użytkownik nie może użyć.
<input role="combobox" aria-autocomplete="list" aria-controls="city-list"> -
aria-busystanCo robi: Informuje, że region jest aktualnie aktualizowany.
Kiedy używać: Przy dynamicznych listach, wynikach wyszukiwania albo panelach, które są w trakcie ładowania.
Kiedy uważać: Nie zostawiaj aria-busy=true po zakończeniu aktualizacji.
Powiązane WCAG: 4.1.3
Ryzyko: Stałe aria-busy=true może blokować lub opóźniać ogłaszanie istotnych zmian.
<section aria-live="polite" aria-busy="true">Ładowanie wyników…</section> -
aria-checkedstanCo robi: Informuje, czy element typu checkbox, radio albo switch jest zaznaczony.
Kiedy używać: Przy niestandardowych checkboxach, radio buttonach i przełącznikach, gdy nie można użyć natywnego inputa.
Kiedy uważać: Nie używaj zamiast natywnego checked na <input type="checkbox">, jeśli natywny input wystarcza.
Powiązane WCAG: 2.1.1, 4.1.2
Ryzyko: Jeżeli stan wizualny i aria-checked są niespójne, użytkownik dostaje błędną informację o stanie kontrolki.
<button role="switch" aria-checked="false">Powiadomienia</button> -
aria-controlswłaściwośćCo robi: Wskazuje element kontrolowany przez daną kontrolkę.
Kiedy używać: Przy akordeonach, disclosure, menu rozwijanych, zakładkach i panelach sterowanych przyciskiem.
Kiedy uważać: Nie używaj jako zamiennika działania JavaScript. Sam atrybut niczego nie otwiera i nie zamyka.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Błędne albo nieistniejące id powoduje fałszywą relację.
<button aria-expanded="false" aria-controls="filters">Pokaż filtry</button> <section id="filters" hidden>...</section> -
aria-currentstanCo robi: Wskazuje aktualny element w zestawie, np. aktualną stronę w nawigacji.
Kiedy używać: W menu, breadcrumbs, paginacji, kalendarzu albo krokach procesu.
Kiedy uważać: Nie używaj do wskazania wybranej zakładki lub opcji w listboxie. Tam zwykle użyj aria-selected.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Mylenie aria-current z aria-selected utrudnia zrozumienie komponentu.
<a href="/szkolenia" aria-current="page">Szkolenia</a> -
aria-describedbywłaściwośćCo robi: Łączy element z dodatkowym opisem.
Kiedy używać: Do instrukcji, podpowiedzi, ograniczeń i komunikatów błędów.
Kiedy uważać: Nie wskazuj bardzo długich treści, które utrudnią korzystanie z formularza.
Powiązane WCAG: 1.3.1, 3.3.1, 3.3.2, 4.1.2
Ryzyko: Nieistniejące id albo zbyt długi opis obniża użyteczność.
<input id="password" aria-describedby="password-help"> <p id="password-help">Minimum 12 znaków.</p> -
aria-descriptionwłaściwośćCo robi: Nadaje elementowi opis dostępny bez odwoływania się do innego elementu w DOM.
Kiedy używać: Gdy krótki opis nie musi być widoczny jako osobny tekst w interfejsie.
Kiedy uważać: Nie używaj zamiast widocznych instrukcji, szczególnie przy formularzach.
Powiązane WCAG: 3.3.2, 4.1.2
Ryzyko: Może ukryć ważną informację przed użytkownikami widzącymi. Jeśli informacja jest ważna, pokaż ją wizualnie.
<button aria-description="Eksportuje dane w formacie CSV">Eksportuj</button> -
aria-detailswłaściwośćCo robi: Wskazuje element zawierający bardziej szczegółowy opis lub strukturę pomocniczą.
Kiedy używać: Gdy element wymaga bogatszego opisu niż krótki tekst, np. z tabelą albo listą.
Kiedy uważać: Nie używaj dla prostych podpowiedzi. Do nich wystarczy aria-describedby.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Wsparcie i sposób obsługi mogą różnić się między technologiami wspomagającymi. Zapewnij też zwykłą widoczną alternatywę, jeśli opis jest krytyczny.
<img src="chart.png" alt="Sprzedaż rośnie w 2026 roku" aria-details="chart-details"> <div id="chart-details">...</div> -
aria-disabledstanCo robi: Informuje, że element jest niedostępny lub wyłączony.
Kiedy używać: Przy niestandardowych komponentach albo gdy wyłączony element ma pozostać w kolejności fokusu.
Kiedy uważać: Dla natywnych przycisków i pól formularza zwykle użyj disabled.
Powiązane WCAG: 4.1.2
Ryzyko: aria-disabled samo nie blokuje kliknięcia ani działania skryptu. Trzeba to obsłużyć w logice.
<button type="button" aria-disabled="true">Następny krok</button> -
aria-errormessagewłaściwośćCo robi: Wskazuje element z komunikatem błędu dla pola.
Kiedy używać: Razem z aria-invalid=true, gdy pole ma aktywny komunikat błędu.
Kiedy uważać: Nie wskazuj komunikatu, który jest ukryty albo nie dotyczy aktualnego błędu.
Powiązane WCAG: 3.3.1, 4.1.2
Ryzyko: W praktyce często bezpieczniejsze jest też użycie aria-describedby, bo wsparcie aria-errormessage może być mniej przewidywalne.
<input aria-invalid="true" aria-errormessage="email-error"> <p id="email-error">Wpisz poprawny e-mail.</p> -
aria-expandedstanCo robi: Informuje, czy kontrolowany obszar jest rozwinięty czy zwinięty.
Kiedy używać: Przy akordeonach, disclosure, menu, drzewach i przyciskach 'pokaż więcej'.
Kiedy uważać: Nie używaj dla elementów, które niczego nie rozwijają ani nie zwijają.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Stan musi być synchronizowany z widocznością. Fałszywe aria-expanded to częsty błąd audytowy.
<button aria-expanded="false" aria-controls="panel">Pokaż szczegóły</button> -
aria-haspopupwłaściwośćCo robi: Informuje, że element otwiera popup określonego typu.
Kiedy używać: Gdy przycisk otwiera menu, listbox, tree, grid albo dialog.
Kiedy uważać: Nie używaj dla zwykłych tooltipów albo linków prowadzących na inną stronę.
Powiązane WCAG: 4.1.2
Ryzyko: Fałszywy typ popupu wprowadza użytkownika w błąd co do zachowania komponentu.
<button aria-haspopup="menu" aria-expanded="false">Opcje</button> -
aria-hiddenstanCo robi: Usuwa element i jego potomków z drzewa dostępności.
Kiedy używać: Dla dekoracji, duplikatów treści albo ikon, które mają już alternatywny tekst.
Kiedy uważać: Nigdy nie ukrywaj elementów fokusowalnych ani interaktywnych.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: aria-hidden=true na rodzicu ukryje także dzieci, w tym przyciski i linki.
<span aria-hidden="true">★</span><span class="sr-only">Ocena 5 na 5</span> -
aria-invalidstanCo robi: Informuje, że wartość pola jest nieprawidłowa.
Kiedy używać: Po walidacji pola, najlepiej razem z widocznym komunikatem błędu.
Kiedy uważać: Nie ustawiaj true od razu na pustym formularzu przed interakcją użytkownika.
Powiązane WCAG: 3.3.1, 4.1.2
Ryzyko: Zbyt wczesne oznaczenie błędu tworzy hałas i może dezorientować użytkownika.
<input aria-invalid="true" aria-describedby="email-error"> -
aria-keyshortcutswłaściwośćCo robi: Informuje o skrótach klawiaturowych powiązanych z elementem.
Kiedy używać: Gdy aplikacja ma własne skróty i użytkownik powinien móc je poznać przez technologię wspomagającą.
Kiedy uważać: Nie używaj dla skrótów, które nie działają albo są sprzeczne ze skrótami przeglądarki i czytnika ekranu.
Powiązane WCAG: 2.1.4, 4.1.2
Ryzyko: Sam atrybut nie tworzy skrótu. Skrót musi faktycznie działać i nie może blokować użytkownika.
<button aria-keyshortcuts="Control+S">Zapisz</button> -
aria-labelwłaściwośćCo robi: Nadaje elementowi nazwę dostępną.
Kiedy używać: Dla elementów bez widocznej etykiety, np. przycisku z samą ikoną.
Kiedy uważać: Nie używaj, jeśli widoczna etykieta już istnieje i może być użyta jako nazwa.
Powiązane WCAG: 2.5.3, 4.1.2
Ryzyko: Nazwa sprzeczna z tekstem widocznym narusza zrozumiałość i może łamać 2.5.3.
<button aria-label="Zamknij okno">×</button> -
aria-labelledbywłaściwośćCo robi: Tworzy nazwę dostępną z treści elementu lub elementów wskazanych przez id.
Kiedy używać: Gdy nazwa jest już widoczna na stronie i można się do niej odwołać.
Kiedy uważać: Nie wskazuj id, które nie istnieje albo jest zduplikowane.
Powiązane WCAG: 1.3.1, 2.5.3, 4.1.2
Ryzyko: aria-labelledby ma pierwszeństwo w wyliczaniu nazwy. Błędne wskazanie może nadpisać dobrą nazwę.
<h2 id="dialog-title">Usuń plik</h2> <div role="dialog" aria-labelledby="dialog-title">...</div> -
aria-levelwłaściwośćCo robi: Określa poziom elementu w strukturze, np. nagłówka, drzewa albo listy hierarchicznej.
Kiedy używać: Przy niestandardowych strukturach, gdy nie da się użyć natywnych nagłówków albo list.
Kiedy uważać: Nie używaj zamiast poprawnej hierarchii <h1>-<h6>, jeśli możesz użyć natywnego HTML.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Niewłaściwy poziom zaburza strukturę dokumentu.
<div role="heading" aria-level="2">Dostępność formularzy</div> -
aria-livewłaściwośćCo robi: Określa, czy zmiany w regionie mają być ogłaszane przez technologie wspomagające.
Kiedy używać: Przy komunikatach statusu, wynikach wyszukiwania, walidacji i powiadomieniach.
Kiedy uważać: Nie ustawiaj assertive dla zwykłych informacji. Nie dawaj aria-live na cały main.
Powiązane WCAG: 4.1.3
Ryzyko: Nadużywanie regionów live powoduje chaos informacyjny.
<p aria-live="polite" id="status"></p> -
aria-modalwłaściwośćCo robi: Informuje, że dialog jest modalny i interakcja powinna być ograniczona do niego.
Kiedy używać: Przy faktycznych modalach z poprawną obsługą fokusu.
Kiedy uważać: Nie używaj, jeśli fokus może przejść poza modal albo tło nadal jest interaktywne.
Powiązane WCAG: 2.1.1, 2.4.3, 4.1.2
Ryzyko: aria-modal bez focus trapu i powrotu fokusu daje fałszywe poczucie dostępności.
<div role="dialog" aria-modal="true" aria-labelledby="title">...</div> -
aria-multilinewłaściwośćCo robi: Informuje, że element tekstowy obsługuje wiele linii.
Kiedy używać: Przy niestandardowym textboxie wielowierszowym.
Kiedy uważać: Nie używaj zamiast natywnego <textarea>, jeśli <textarea> wystarcza.
Powiązane WCAG: 4.1.2
Ryzyko: Niestandardowy textbox wymaga obsługi klawiatury, fokusu i edycji tekstu.
<div role="textbox" aria-multiline="true" contenteditable="true"></div> -
aria-multiselectablewłaściwośćCo robi: Informuje, że w komponencie można wybrać więcej niż jeden element.
Kiedy używać: Przy listboxie, gridzie albo tree z wielokrotnym wyborem.
Kiedy uważać: Nie używaj, jeśli interfejs pozwala wybrać tylko jedną opcję.
Powiązane WCAG: 4.1.2
Ryzyko: Użytkownik musi dostać jasną instrukcję, jak zaznaczać wiele elementów klawiaturą.
<ul role="listbox" aria-multiselectable="true">...</ul> -
aria-orientationwłaściwośćCo robi: Określa orientację komponentu: poziomą albo pionową.
Kiedy używać: Przy tablist, separatorach, suwakach i innych widgetach, gdzie orientacja wpływa na obsługę klawiatury.
Kiedy uważać: Nie dodawaj, jeśli orientacja jest domyślna i oczywista dla danego wzorca.
Powiązane WCAG: 2.1.1, 4.1.2
Ryzyko: Orientacja musi pasować do realnej obsługi strzałkami.
<div role="tablist" aria-orientation="vertical">...</div> -
aria-ownswłaściwośćCo robi: Tworzy relację rodzic-dziecko w drzewie dostępności niezależnie od struktury DOM.
Kiedy używać: Tylko w rzadkich przypadkach złożonych komponentów, gdy elementy logicznie należą do komponentu, ale nie są jego potomkami w DOM.
Kiedy uważać: Unikaj jako domyślnego sposobu naprawiania złej struktury HTML. Najpierw popraw DOM.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Może zmienić kolejność czytania i stworzyć niespójność między DOM a drzewem dostępności.
<div role="listbox" aria-owns="option-a option-b"></div> -
aria-placeholderwłaściwośćCo robi: Przekazuje tekst zastępczy dla niestandardowego pola tekstowego.
Kiedy używać: Przy niestandardowym role=textbox, gdy nie można użyć natywnego placeholdera.
Kiedy uważać: Nie używaj placeholdera zamiast etykiety. Placeholder znika i nie jest dobrą instrukcją.
Powiązane WCAG: 3.3.2, 4.1.2
Ryzyko: Brak widocznej etykiety nadal będzie problemem, nawet jeśli placeholder istnieje.
<div role="textbox" aria-placeholder="Wpisz komentarz" contenteditable="true"></div> -
aria-posinsetwłaściwośćCo robi: Określa pozycję elementu w zestawie.
Kiedy używać: Gdy nie wszystkie elementy zestawu są obecne w DOM, np. przy wirtualizowanych listach.
Kiedy uważać: Nie używaj w zwykłych listach HTML, gdzie przeglądarka może sama określić pozycję.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Błędna pozycja dezorientuje użytkownika, szczególnie w długich listach.
<div role="option" aria-posinset="7" aria-setsize="120">Wynik 7</div> -
aria-pressedstanCo robi: Informuje, że przycisk działa jako przełącznik i czy jest wciśnięty.
Kiedy używać: Przy przyciskach typu toggle, np. włącz/wyłącz tryb, pogrubienie, wyciszenie.
Kiedy uważać: Nie używaj do rozwijania i zwijania sekcji. Tam użyj aria-expanded.
Powiązane WCAG: 4.1.2
Ryzyko: Stan musi zmieniać się po aktywacji i pasować do wizualnego wyglądu.
<button aria-pressed="false">Tryb ciemny</button> -
aria-readonlystanCo robi: Informuje, że element jest tylko do odczytu.
Kiedy używać: Przy polach lub komórkach gridu, które można odczytać, ale nie edytować.
Kiedy uważać: Dla natywnych pól formularza użyj readonly, jeśli wystarcza.
Powiązane WCAG: 4.1.2
Ryzyko: aria-readonly samo nie blokuje edycji w niestandardowym komponencie.
<input value="ABC-123" readonly> -
aria-relevantwłaściwośćCo robi: Określa, jakie typy zmian w regionie live są istotne do ogłoszenia.
Kiedy używać: W regionach live, gdy trzeba kontrolować, czy ogłaszane są dodania, usunięcia albo zmiany tekstu.
Kiedy uważać: Nie komplikuj bez potrzeby. Często wystarczy aria-live i krótki komunikat statusu.
Powiązane WCAG: 4.1.3
Ryzyko: Różnice wsparcia między technologiami mogą sprawić, że efekt nie będzie identyczny wszędzie.
<ul aria-live="polite" aria-relevant="additions text"></ul> -
aria-requiredstanCo robi: Informuje, że użytkownik musi uzupełnić pole przed wysłaniem formularza.
Kiedy używać: Przy niestandardowych kontrolkach albo jako uzupełnienie widocznej informacji o wymaganym polu.
Kiedy uważać: Nie używaj jako jedynej informacji o wymaganiu. Użytkownik widzący też musi to wiedzieć.
Powiązane WCAG: 3.3.2, 4.1.2
Ryzyko: Dla natywnych pól required jest zwykle lepsze, bo wpływa też na zachowanie formularza.
<div role="textbox" aria-required="true" aria-labelledby="comment-label"></div> -
aria-roledescriptionwłaściwośćCo robi: Zmienia sposób prezentowania roli elementu przez technologie wspomagające.
Kiedy używać: Bardzo ostrożnie, tylko gdy standardowa rola jest poprawna, ale potrzebuje bardziej zrozumiałego opisu kontekstowego.
Kiedy uważać: Nie używaj do maskowania złej roli ani do wymyślania nazw, które utrudnią zrozumienie komponentu.
Powiązane WCAG: 4.1.2
Ryzyko: Może ukryć standardową rolę i pogorszyć przewidywalność. Używać wyjątkowo.
<section role="region" aria-roledescription="slajd" aria-labelledby="slide-title">...</section> -
aria-selectedstanCo robi: Informuje, który element jest wybrany w złożonym komponencie.
Kiedy używać: Przy zakładkach, opcjach listboxa, wierszach lub komórkach w gridzie.
Kiedy uważać: Nie używaj w zwykłej nawigacji do oznaczenia aktualnej strony. Tam użyj aria-current.
Powiązane WCAG: 4.1.2
Ryzyko: Stan musi odpowiadać realnie aktywnej lub wybranej opcji.
<button role="tab" aria-selected="true">WCAG</button> -
aria-setsizewłaściwośćCo robi: Określa łączną liczbę elementów w zestawie.
Kiedy używać: Razem z aria-posinset, szczególnie przy wirtualizowanych listach.
Kiedy uważać: Nie używaj w zwykłych listach, gdzie wszystkie elementy są w DOM.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Błędny rozmiar zestawu może sugerować, że użytkownik ma mniej lub więcej opcji niż faktycznie.
<div role="option" aria-posinset="7" aria-setsize="120">Wynik 7</div> -
aria-sortwłaściwośćCo robi: Informuje o kierunku sortowania kolumny lub wiersza.
Kiedy używać: Przy tabelach danych z sortowalnymi nagłówkami.
Kiedy uważać: Nie dodawaj na wiele kolumn jednocześnie, jeśli tylko jedna kolumna określa bieżące sortowanie.
Powiązane WCAG: 1.3.1, 4.1.2
Ryzyko: Stan sortowania musi być aktualizowany po każdej zmianie sortowania.
<th scope="col" aria-sort="ascending"><button>Nazwa</button></th> -
aria-valuemin / aria-valuemax / aria-valuenow / aria-valuetextwłaściwośćCo robi: Opisują wartość, zakres i tekstową reprezentację wartości w komponentach liczbowych.
Kiedy używać: Przy sliderach, progressbarach, scrollbarach i spinbuttonach, szczególnie niestandardowych.
Kiedy uważać: Nie używaj, jeśli natywny element, np. <input type="range"> albo <progress>, wystarcza.
Powiązane WCAG: 4.1.2
Ryzyko: Niestandardowy slider wymaga pełnej obsługi klawiatury i aktualizacji wartości.
<div role="slider" tabindex="0" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50" aria-label="Głośność"></div>
Gotowy wzorzec: dostępny komunikat błędu w formularzu
<form novalidate>
<div class="field">
<label for="email">Adres e-mail</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
required
aria-invalid="true"
aria-describedby="email-error">
<p id="email-error">
Wpisz poprawny adres e-mail, np. jan@example.com.
</p>
</div>
<button type="submit">
Wyślij
</button>
</form>Dlaczego to działa:
- pole ma widoczną etykietę przez
<label>, - pole jest wymagane przez
required, - błąd jest widoczny tekstowo,
aria-invalid="true"przekazuje stan błędu,aria-describedbyłączy pole z komunikatem błędu.
Ten wzorzec wspiera wymagania WCAG 3.3.1, 3.3.2 i 4.1.2.
Gotowy wzorzec: przycisk rozwijający sekcję
<button
type="button"
aria-expanded="false"
aria-controls="details">
Pokaż szczegóły
</button>
<div id="details" hidden>
<p>
Szkolenie obejmuje analizę WCAG, testy klawiaturą i pracę z czytnikiem ekranu.
</p>
</div>Minimum działania:
const button = document.querySelector('[aria-controls="details"]');
const panel = document.querySelector('#details');
button.addEventListener('click', () => {
const expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!expanded));
panel.hidden = expanded;
button.textContent = expanded ? 'Pokaż szczegóły' : 'Ukryj szczegóły';
});To działa, bo:
- aktywatorem jest natywny
<button>, - stan
aria-expandedzgadza się ze stanem panelu, - relacja z panelem jest wskazana przez
aria-controls, - treść ukryta jest przez natywny atrybut
hidden.
Gotowy wzorzec: komunikat dynamiczny
<button type="button" id="save-button">
Zapisz zmiany
</button>
<p id="save-status" aria-live="polite" aria-atomic="true"></p>const button = document.querySelector('#save-button');
const status = document.querySelector('#save-status');
button.addEventListener('click', async () => {
status.textContent = 'Zapisywanie zmian…';
await saveChanges();
status.textContent = 'Zmiany zostały zapisane.';
});To działa, bo komunikat o stanie jest przekazany bez przenoszenia fokusu. To wspiera WCAG 4.1.3.
Najczęstsze błędy ARIA
| Błąd | Dlaczego to problem | Poprawka |
|---|---|---|
div role="button" bez obsługi klawiatury | Czytnik ekranu ogłasza przycisk, ale użytkownik klawiatury nie może go aktywować jak przycisku. | Użyj <button> albo dodaj pełną obsługę klawiatury. |
aria-label sprzeczne z widocznym tekstem | Użytkownicy widzący i korzystający z technologii wspomagających dostają różne informacje. | Nazwa dostępna powinna zawierać widoczny tekst. |
aria-expanded niezgodne ze stanem komponentu | Czytnik ekranu dostaje fałszywą informację. | Aktualizuj atrybut przy każdej zmianie widoczności. |
aria-hidden="true" na rodzicu przycisku | Element może zniknąć z drzewa dostępności. | Nie ukrywaj interaktywnych treści przed technologiami wspomagającymi. |
aria-describedby wskazuje nieistniejące id | Opis nie zostanie powiązany z elementem. | Sprawdź unikalność i istnienie id. |
aria-required bez widocznego oznaczenia | Użytkownicy widzący nie wiedzą, że pole jest wymagane. | Dodaj tekst, np. „wymagane” albo gwiazdkę z wyjaśnieniem. |
role="alert" tworzony dopiero w momencie błędu | Niektóre czytniki mogą nie ogłosić komunikatu. | Kontener live powinien istnieć w DOM wcześniej. |
Nadużywanie aria-live="assertive" | Komunikaty przerywają użytkownikowi pracę. | Domyślnie używaj polite; assertive tylko dla krytycznych informacji. |
aria-selected w zwykłej nawigacji | Myli stan wyboru z aktualną stroną. | W nawigacji użyj aria-current="page". |
| ARIA zamiast natywnego HTML | Trzeba samodzielnie odtwarzać zachowania przeglądarki. | Zacznij od semantycznego HTML. |
Jak testować ARIA?
1. Sprawdź semantykę w DevTools
W Chrome albo Edge:
- Otwórz DevTools.
- Wybierz element.
- Przejdź do panelu Accessibility.
- Sprawdź:
- nazwę dostępną,
- rolę,
- stany,
- opis,
- źródło nazwy.
2. Przetestuj klawiaturą
Sprawdź:
- Czy można dojść do elementu klawiszem
Tab. - Czy element aktywuje się
Enteremi/lub spacją. - Czy fokus nie znika.
- Czy kolejność fokusu jest logiczna.
- Czy modal zatrzymuje fokus wewnątrz.
- Czy po zamknięciu modala fokus wraca na element otwierający.
3. Przetestuj czytnikiem ekranu
Minimum:
- Windows: NVDA + Firefox albo Chrome,
- macOS: VoiceOver + Safari,
- mobile: VoiceOver iOS albo TalkBack Android.
Sprawdź, czy czytnik ogłasza:
- poprawną nazwę,
- poprawną rolę,
- poprawny stan,
- komunikaty błędów,
- zmiany w regionach live.
4. Uruchom automatyczne skanery
Narzędzia:
- axe DevTools,
- WAVE,
- Lighthouse,
- ARC Toolkit,
- W3C Markup Validation Service.
Automat może wykryć między innymi:
- niepoprawne role,
- niedozwolone atrybuty ARIA,
- brak nazwy dostępnej,
- błędne
aria-*, - odwołania do nieistniejących identyfikatorów.
Automat nie powie jednak w pełni, czy komponent jest zrozumiały i wygodny. To trzeba sprawdzić manualnie.
Kryteria oceny: kiedy ARIA jest wdrożona dobrze?
ARIA jest wdrożona dobrze, gdy:
- element ma właściwą rolę,
- element ma poprawną nazwę dostępną,
- stan ARIA zgadza się ze stanem wizualnym,
- atrybuty wskazujące na
idprowadzą do istniejących elementów, - komponent działa z klawiatury,
- fokus jest widoczny i logiczny,
- komunikaty dynamiczne są ogłaszane w odpowiednim momencie,
- ARIA nie zastępuje natywnego HTML tam, gdzie HTML wystarcza,
- użytkownik czytnika ekranu dostaje tę samą informację i funkcję co użytkownik widzący.
ARIA jest wdrożona źle, gdy:
- opisuje coś inaczej niż pokazuje interfejs,
- ukrywa ważne treści,
- tworzy fałszywe role,
- nie jest aktualizowana dynamicznie,
- wymaga od użytkownika zgadywania,
- działa tylko wizualnie, ale nie działa z klawiatury i technologiami wspomagającymi.
Krótka ściąga: którego atrybutu użyć?
| Potrzeba | Użyj | Nie używaj błędnie |
|---|---|---|
| Przycisk z samą ikoną | aria-label | Nie dawaj nazwy innej niż znaczenie ikony. |
| Nazwa z widocznego tekstu obok | aria-labelledby | Nie wskazuj nieistniejącego id. |
| Dodatkowa instrukcja | aria-describedby | Nie wrzucaj tam długiego regulaminu. |
| Sekcja rozwijana | aria-expanded + opcjonalnie aria-controls | Nie używaj aria-pressed. |
| Aktualna strona w menu | aria-current="page" | Nie używaj aria-selected. |
| Wybrana zakładka | aria-selected | Nie używaj aria-current. |
| Przycisk przełącznik | aria-pressed | Nie używaj do akordeonu. |
| Pole z błędem | aria-invalid + aria-describedby | Nie ustawiaj błędu przed interakcją użytkownika. |
| Komunikat dynamiczny | aria-live="polite" | Nie dawaj assertive na wszystko. |
| Pełny odczyt krótkiego statusu | aria-atomic="true" | Nie stosuj na dużych sekcjach. |
| Ukrycie dekoracji przed AT | aria-hidden="true" | Nie ukrywaj elementów fokusowalnych. |
| Modal | role="dialog" + aria-modal="true" + nazwa | Nie zapominaj o obsłudze fokusu. |