ARIA bez magii: kiedy pomaga, kiedy psuje dostępność

Najpierw semantyczny HTML, dopiero potem role, stany i właściwość ARIA.

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:

KryteriumZnaczenie w kontekście ARIA
1.3.1 Informacje i relacjeRelacje 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 KlawiaturaJeżeli tworzysz komponent z ARIA, musi działać z klawiatury.
2.4.3 Kolejność fokusuElementy interaktywne muszą mieć logiczną kolejność fokusu.
2.4.7 Widoczny fokusUżytkownik klawiatury musi widzieć, gdzie aktualnie jest fokus.
2.5.3 Etykieta w nazwieWidoczna etykieta powinna być częścią nazwy dostępnej. Szczególnie ważne przy aria-label.
3.3.1 Identyfikacja błęduBłędy muszą być wskazane tekstowo. ARIA może połączyć pole z komunikatem błędu.
3.3.2 Etykiety lub instrukcjeUż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 stanieKomunikaty dynamiczne mogą być przekazywane przez regiony live bez przenoszenia fokusu.

Trzy grupy ARIA

ARIA można praktycznie podzielić na trzy grupy.

GrupaPrzykładyDo czego służy
Rolerole="button", role="dialog", role="alert", role="tab"Mówią, czym jest element.
Stanyaria-expanded, aria-checked, aria-selected, aria-invalidMówią, w jakim stanie jest element.
Właściwościaria-label, aria-labelledby, aria-describedby, aria-controlsDodają 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" rola

    Co 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" rola

    Co 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 div lub span tylko 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" rola

    Co 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" rola

    Co 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" rola

    Co 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-checked musi być aktualizowany razem ze stanem wizualnym.

    <button type="button" role="switch" aria-checked="false">
      Powiadomienia
    </button>
  • role="tab" rola

    Co robi: Oznacza zakładkę w komponencie kart, zwykle razem z tablist i tabpanel.

    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-selected i poprawnych relacji z panelami.

    <div role="tablist" aria-label="Sekcje">
      <button role="tab" aria-selected="true" aria-controls="panel-a">A</button>
    </div>
  • aria-activedescendant wł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-atomic wł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-autocomplete wł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-busy stan

    Co 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-checked stan

    Co 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-controls wł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-current stan

    Co 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-describedby wł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-description wł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-details wł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-disabled stan

    Co 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-errormessage wł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-expanded stan

    Co 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-haspopup wł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-hidden stan

    Co 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-invalid stan

    Co 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-keyshortcuts wł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-label wł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-labelledby wł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-level wł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-live wł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-modal wł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-multiline wł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-multiselectable wł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-orientation wł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-owns wł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-placeholder wł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-posinset wł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-pressed stan

    Co 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-readonly stan

    Co 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-relevant wł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-required stan

    Co 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-roledescription wł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-selected stan

    Co 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-setsize wł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-sort wł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-valuetext wł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-expanded zgadza 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łądDlaczego to problemPoprawka
div role="button" bez obsługi klawiaturyCzytnik 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 tekstemUż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 komponentuCzytnik ekranu dostaje fałszywą informację.Aktualizuj atrybut przy każdej zmianie widoczności.
aria-hidden="true" na rodzicu przyciskuElement może zniknąć z drzewa dostępności.Nie ukrywaj interaktywnych treści przed technologiami wspomagającymi.
aria-describedby wskazuje nieistniejące idOpis nie zostanie powiązany z elementem.Sprawdź unikalność i istnienie id.
aria-required bez widocznego oznaczeniaUż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łęduNiektó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 nawigacjiMyli stan wyboru z aktualną stroną.W nawigacji użyj aria-current="page".
ARIA zamiast natywnego HTMLTrzeba samodzielnie odtwarzać zachowania przeglądarki.Zacznij od semantycznego HTML.

Jak testować ARIA?

1. Sprawdź semantykę w DevTools

W Chrome albo Edge:

  1. Otwórz DevTools.
  2. Wybierz element.
  3. Przejdź do panelu Accessibility.
  4. Sprawdź:
  • nazwę dostępną,
  • rolę,
  • stany,
  • opis,
  • źródło nazwy.

2. Przetestuj klawiaturą

Sprawdź:

  1. Czy można dojść do elementu klawiszem Tab.
  2. Czy element aktywuje się Enterem i/lub spacją.
  3. Czy fokus nie znika.
  4. Czy kolejność fokusu jest logiczna.
  5. Czy modal zatrzymuje fokus wewnątrz.
  6. 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 id prowadzą 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ć?

PotrzebaUżyjNie używaj błędnie
Przycisk z samą ikonąaria-labelNie dawaj nazwy innej niż znaczenie ikony.
Nazwa z widocznego tekstu obokaria-labelledbyNie wskazuj nieistniejącego id.
Dodatkowa instrukcjaaria-describedbyNie wrzucaj tam długiego regulaminu.
Sekcja rozwijanaaria-expanded + opcjonalnie aria-controlsNie używaj aria-pressed.
Aktualna strona w menuaria-current="page"Nie używaj aria-selected.
Wybrana zakładkaaria-selectedNie używaj aria-current.
Przycisk przełącznikaria-pressedNie używaj do akordeonu.
Pole z błędemaria-invalid + aria-describedbyNie ustawiaj błędu przed interakcją użytkownika.
Komunikat dynamicznyaria-live="polite"Nie dawaj assertive na wszystko.
Pełny odczyt krótkiego statusuaria-atomic="true"Nie stosuj na dużych sekcjach.
Ukrycie dekoracji przed ATaria-hidden="true"Nie ukrywaj elementów fokusowalnych.
Modalrole="dialog" + aria-modal="true" + nazwaNie zapominaj o obsłudze fokusu.

Źródła