Kobieta trzymająca pustą butelkę z pigułkami kontrolującą jakość w fabryce

Dostępne etykiety formularzy – jak używać aria-label i label

14 min. czytania

Dostępne formularze zaczynają się od dobrze zrobionych etykiet.

Zawsze używaj <label> jako podstawy, a aria-label traktuj jako narzędzie pomocnicze tylko tam, gdzie standardowy HTML nie wystarcza.

Poniżej znajdziesz szczegółowy, praktyczny przewodnik po tym, jak projektować dostępne etykiety formularzy, jak łączyć pola z label oraz kiedy i jak bezpiecznie używać aria-label.

Dlaczego etykiety są kluczowe dla dostępności formularzy

WCAG wymaga, aby każde pole formularza miało jasną etykietę lub instrukcję, która opisuje cel pola, zanim użytkownik zacznie z nim wchodzić w interakcję.

Dotyczy to wszystkich elementów proszących o dane: input, textarea, select, przycisków, przełączników itd.

Kilka powodów, dla których etykiety są tak ważne:

  • czytniki ekranu – użytkownicy słyszą nazwę pola zamiast widzieć układ,;
  • sterowanie głosem (np. Dragon NaturallySpeaking) – komenda „Kliknij E-mail” odwołuje się do dostępnej nazwy/etykiety,;
  • mniej błędów i frustracji – lepsze etykiety zmniejszają pomyłki i wspierają WCAG 3.3.2 „Etykiety lub instrukcje”.

Normy dostępności (np. WCAG 1.3.1 „Informacje i relacje” oraz 2.4.6 „Nagłówki i etykiety”) wymagają, by relacje w strukturze (np. pole–etykieta) były odzwierciedlone w kodzie, a etykiety były zrozumiałe także poza kontekstem wizualnym.

Co to jest „dostępna etykieta” (dostępna nazwa)

W świecie dostępności mówi się o dostępnej nazwie (accessible name).

To tekst, który technologie asystujące kojarzą z elementem interaktywnym – np. polem formularza, przyciskiem czy linkiem.

Źródła dostępnej nazwy mogą być różne:

  • <label for="…"> – podstawowy, preferowany sposób etykietowania formularzy;
  • tekst w <legend> – dla grupy pól w fieldset;
  • atrybut alt – dla obrazka, np. przycisku w formie ikonografii;
  • atrybuty ARIAaria-label lub aria-labelledby.

Ważne: ARIA może nadpisać naturalną dostępną nazwę – jeśli pole ma label, ale dodasz aria-label, czytnik ekranu użyje tekstu z aria-label zamiast z label. Dlatego aria-label stosuj z umiarem i pełną świadomością konsekwencji.

Zasada nadrzędna – najpierw semantyczny HTML, dopiero potem ARIA

Oficjalne wytyczne (np. gov.pl, WCAG) mówią jasno:

  • najpierw semantyczny HTML – użyj label, fieldset, legend, właściwego typu input;
  • ARIA jako dodatek – gdy natywne elementy nie wystarczają, np. dla niestandardowych kontrolek JS;
  • ARIA nie jest pierwszym wyborem – to wsparcie, nie zamiennik semantyki.

W praktyce: jeśli możesz użyć label, użyj label.

aria-label stosuj dopiero wtedy, gdy naprawdę nie masz możliwości użycia standardowej etykiety HTML (np. przyciski tylko z ikoną, niestandardowe widżety, kontrolki bez tekstu).

Jak poprawnie używać <label> w formularzach

Podstawowy wzorzec: for + id

Najbardziej solidny sposób powiązania etykiety z polem to element label z atrybutem for wskazującym id kontrolki. Oto prosty przykład:

<label for="email">Adres e-mail</label>

<input type="email" id="email" name="email">

Co to daje w praktyce:

  • atrybut for w label ma identyczną wartość jak id w polu,
  • kliknięcie w tekst etykiety ustawia fokus w odpowiednim polu,
  • czytnik ekranu odczytuje nazwę: „Adres e-mail, pole edycji…”.

Na gov.pl wskazuje się, że do pojedynczego pola można zastosować tylko jeden element <label>. Dodatkowe informacje (np. pomoc, komunikaty o błędach) należy przekazywać inaczej – np. przez aria-describedby.

Etykieta „opakowująca” pole (implicit label)

Można też zastosować etykietę „opakowującą”. Przykład kodu:

<label>

  Adres e-mail

  <input type="email" name="email">

</label>

To również tworzy poprawne powiązanie etykiety z polem (tzw. implicit label). W większych projektach częściej preferuje się jawne for/id, bo ułatwia to stylowanie i testowanie oraz pozwala elastycznie łączyć tekst z wieloma kontrolkami (np. przez aria-labelledby).

Etykieta dla pól tekstowych, select, textarea

Każde standardowe pole proszące o dane powinno mieć label. Przykłady kodu:

<label for="name">Imię i nazwisko</label>

<input type="text" id="name" name="name">

<label for="message">Wiadomość</label>

<textarea id="message" name="message"></textarea>

<label for="country">Kraj</label>

<select id="country" name="country">

  <option value="">Wybierz kraj</option>

  <option value="pl">Polska</option>

  <option value="de">Niemcy</option>

</select>

WCAG 3.3.2 wymaga, aby etykieta lub instrukcja jasno określała, jakie informacje należy wprowadzić, zanim użytkownik zacznie wypełniać pole.

Etykieta dla pól typu checkbox i radio

Checkboxy i radiobuttony również muszą mieć etykiety. Przykład checkboxa:

<input type="checkbox" id="agree" name="agree">

<label for="agree">Akceptuję regulamin</label>

Alternatywnie (etykieta opakowująca):

<label for="agree">

  <input type="checkbox" id="agree" name="agree">

  Akceptuję regulamin

</label>

Przykład dla radiobuttonów (z grupowaniem):

<fieldset>

  <legend>Preferowana forma kontaktu</legend>

  <input type="radio" id="contact-email" name="contact" value="email">

  <label for="contact-email">E-mail</label>

  <input type="radio" id="contact-phone" name="contact" value="phone">

  <label for="contact-phone">Telefon</label>

</fieldset>

<legend> opisuje grupę, a każdy input ma własne <label>.

Pola obowiązkowe: required i ARIA

Pola wymagane powinny być oznaczone zarówno wizualnie, jak i programistycznie. Przykład kodu:

<label for="email">Adres e-mail <span aria-hidden="true">*</span></label>

<input type="email" id="email" name="email" required>

Warto pamiętać o trzech zasadach:

  • gwiazdka informuje użytkowników widzących,
  • atrybut required sygnalizuje technologiom asystującym, że pole jest obowiązkowe,
  • w niestandardowych kontrolkach można dodatkowo użyć aria-required="true".

Grupowanie pól: fieldset i legend

Przy kilku powiązanych polach (np. grupa radiobuttonów, dane adresowe) stosuj fieldset i legend. Przykład kodu:

<fieldset>

  <legend>Dane do faktury</legend>

  <label for="company">Nazwa firmy</label>

  <input type="text" id="company" name="company">

  <label for="nip">NIP</label>

  <input type="text" id="nip" name="nip">

</fieldset>

Wielu czytników ekranu uwzględni <legend> w dostępnej nazwie pól, co pomaga zrozumieć kontekst bez polegania na samym układzie wizualnym.

Placeholder to nie etykieta

Nie używaj placeholdera jako etykiety. Wytyczne WCAG oraz materiały edukacyjne podkreślają, że:

  • placeholder jest podpowiedzią, a nie etykietą,
  • tekst placeholdera znika po wpisaniu pierwszych znaków,
  • czytniki ekranu traktują placeholder inaczej niż etykietę.

Zawsze dodawaj <label>. placeholder może pełnić rolę uzupełniającej podpowiedzi, ale nie zastępuje etykiety.

Atrybut aria-label – czym jest i kiedy go używać

aria-label dostarcza programistycznej etykiety elementowi, który jej naturalnie nie ma. Jest szczególnie przydatny, gdy:

  • element nie ma widocznego tekstu,
  • nie można zastosować label lub alt,
  • trzeba przekazać krótką nazwę wyłącznie dla technologii asystujących.

Stosuj go tylko wtedy, gdy to naprawdę konieczne; w wielu przypadkach lepsze są standardowe etykiety HTML (label dla pól formularzy, alt dla obrazów).

Atrybut aria-label nadpisuje etykietę

Jeśli element ma naturalną etykietę (np. label), a dodasz aria-label, ARIA nadpisze tę etykietę w dostępnej nazwie. Przykład:

<label for="phone">Telefon kontaktowy</label>

<input id="phone" name="phone" aria-label="Numer telefonu">

Czytnik ekranu może odczytać: „Numer telefonu, pole edycji…”, a nie „Telefon kontaktowy…”. Niespójność treści wprowadza zamieszanie.

Dlatego nie duplikuj label za pomocą aria-label ani nie „koryguj” widocznego tekstu wyłącznie dla czytników – zmień etykietę wizualną lub użyj aria-describedby do doprecyzowania.

Typowe, poprawne zastosowania aria-label

  1. Przyciski z samą ikoną. Przykład:

<button type="submit" aria-label="Wyślij formularz">

  <svg aria-hidden="true" focusable="false">…</svg>

</button>

Element nie ma tekstu, zatem aria-label dostarcza nazwę. Ikonie nadaj aria-hidden="true", aby nie była odczytywana osobno.

  1. Linki będące tylko ikoną. Przykład:

<a href="#top" aria-label="Przejdź na początek strony">

  <svg aria-hidden="true" focusable="false">…</svg>

</a>

  1. Niestandardowe kontrolki JS. Przykład:

<div role="switch" aria-checked="false" aria-label="Powiadomienia e-mail"></div>

W tym przypadku rola jest nadana przez ARIA (role="switch"), a aria-label dostarcza nazwę.

Atrybut aria-labelledby – gdy masz już widoczny tekst

W wielu przypadkach lepszym wyborem niż aria-label jest aria-labelledby, bo wskazuje istniejący element tekstowy, którego treść ma zostać wykorzystana jako nazwa.

Prosty przypadek z natywnym label (atrybut niepotrzebny):

<label id="search-label" for="search">Szukaj w serwisie</label>

<input type="search" id="search" name="search">

Przy komponentach niestandardowych zastosuj aria-labelledby do podłączenia istniejącego tekstu jako nazwy. Przykład:

<span id="slider-label">Głośność</span>

<div role="slider" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50" aria-labelledby="slider-label"></div>

Dzięki temu korzystasz z widocznego tekstu i utrzymujesz spójność między tym, co widzi użytkownik, a tym, co „słyszy” czytnik.

Atrybut aria-describedby – podpowiedzi i komunikaty, a nie etykiety

aria-describedby nie jest etykietą, ale uzupełniającym opisem. Świetnie nadaje się do:

  • wskazówek, jak wypełnić pole,
  • informacji o formacie (np. „Wprowadź datę w formacie RRRR-MM-DD”),
  • komunikatów o błędach.

Przykład z podpowiedzią:

<label for="date">Data urodzenia</label>

<span id="date-hint">W formacie RRRR-MM-DD, np. 1980-05-21</span>

<input type="text" id="date" name="date" aria-describedby="date-hint">

<label> dostarcza nazwę pola, a aria-describedby dodaje opis, który czytnik dołącza do odczytu.

Podobnie przy błędach:

<label for="email">Adres e-mail</label>

<span id="email-error" class="error">Podaj poprawny adres e-mail.</span>

<input type="email" id="email" name="email" aria-describedby="email-error" aria-invalid="true">

aria-invalid="true" sygnalizuje błąd, a aria-describedby wskazuje powiązany komunikat.

Praktyczne wzorce użycia label i ARIA w formularzach

Standardowe pola tekstowe z podpowiedzią

Przykład kompletnego pola z etykietą i podpowiedzią:

<div class="form-group">

  <label for="username">Nazwa użytkownika</label>

  <span id="username-hint">Użyj 6–20 znaków, bez spacji.</span>

  <input type="text" id="username" name="username" aria-describedby="username-hint">

</div>

Etykieta (label) zapewnia nazwę, a widoczna podpowiedź połączona przez aria-describedby – opis.

Pole wyszukiwania z ikoną

Nie opieraj się tylko na placeholderze. Dodaj etykietę (może być ukryta wizualnie):

<label class="visually-hidden" for="site-search">Szukaj w serwisie</label>

<input type="search" id="site-search" name="q" placeholder="Szukaj…" />

<button type="submit">

  <span class="visually-hidden">Szukaj</span>

  <svg aria-hidden="true" focusable="false">…</svg>

</button>

Ukryty wizualnie label nadal jest dostępny dla czytników. Dla przycisków z ikoną możesz użyć ukrytego tekstu zamiast aria-label, aby dostępna nazwa wynikała z realnego węzła tekstowego.

Przyciski tylko z ikoną – aria-label

Gdy nie możesz dodać ukrytego tekstu, zastosuj aria-label:

<button type="button" aria-label="Usuń element">

  <svg aria-hidden="true" focusable="false">…</svg>

</button>

Upewnij się, że tekst aria-label jest krótki i jednoznaczny oraz zgodny z tym, co użytkownik widzi (np. tooltipem).

Najczęstsze błędy przy label i aria-label

Brak programistycznego powiązania etykiety z polem

Niepoprawnie:

<p>Adres e-mail</p>

<input type="email">

Wizualnie wygląda jak etykieta, ale brakuje <label> – czytnik ekranu nie widzi relacji.

Poprawnie:

<label for="email">Adres e-mail</label>

<input type="email" id="email" name="email">

Używanie aria-label zamiast label tam, gdzie label jest możliwe

Przykład niezalecany:

<input type="text" aria-label="Imię i nazwisko">

Zalecane rozwiązanie:

<label for="fullname">Imię i nazwisko</label>

<input type="text" id="fullname" name="fullname">

ARIA stosuj głównie wtedy, gdy natywny HTML nie wystarcza.

Duplikowanie i niespójność etykiet

Błędny przykład:

<label for="phone">Telefon</label>

<input id="phone" name="phone" aria-label="Numer telefonu kontaktowego">

Czytnik użyje aria-label („Numer telefonu kontaktowego”), podczas gdy użytkownik widzi „Telefon”. To mylące dla części użytkowników i narzędzi sterowania głosem.

Jeśli istnieje label, nie dodawaj aria-label z inną treścią; gdy potrzebujesz doprecyzować informację, użyj aria-describedby.

Ukrywanie etykiety zamiast poprawnej implementacji

Usuwanie etykiet „dla czystości” interfejsu i poleganie wyłącznie na placeholderach narusza WCAG 3.3.2 oraz 1.3.1. Etykieta musi być stabilna i zawsze dostępna.

Rozbieżność między etykietą a tym, co się dzieje

WCAG 2.4.6 podkreśla, że etykiety powinny dokładnie opisywać cel elementu. Jeśli pole wymaga konkretnego formatu, poinformuj o tym użytkownika. Przykład:

<label for="email">Adres e-mail (służbowy)</label>

<span id="email-hint">Podaj adres w domenie Twojej firmy, np. [email protected]</span>

<input type="email" id="email" name="email" aria-describedby="email-hint">

Testowanie etykiet formularzy

Nawet najlepiej zaprojektowany kod trzeba przetestować z perspektywy użytkownika.

Narzędzia automatyczne

Skorzystaj z axe DevTools lub Lighthouse w Chrome, aby wychwycić brakujące etykiety i podstawowe błędy struktury. Uzupełnij to audytem w narzędziu WAVE, które pokaże pola bez etykiet i problemy z ARIA.

Testy z czytnikami ekranu

Zalecane jest regularne testowanie z popularnymi czytnikami. Krótkie wskazówki:

  • NVDA + Firefox – użyj klawisza F do przechodzenia między polami i słuchaj, jaka etykieta jest odczytywana; oceń, czy sama etykieta wystarczy do zrozumienia celu pola;
  • JAWS + Chrome – włącz Forms Mode (F) i przejrzyj listę kontrolek formularza; zwróć uwagę na rozbieżności między tekstem na ekranie a tym, co czyta JAWS;
  • VoiceOver + Safari (macOS/iOS) – użyj Web Rotor, przejdź do listy kontrolek formularza; upewnij się, że nazwy elementów są zrozumiałe bez kontekstu strony.

Prosty test manualny

Dobra praktyka: „zakryj” sobie wizualnie wszystko oprócz pola i jego etykiety, po czym odpowiedz na pytanie:

Czy tylko na podstawie tej etykiety wiem, co mam tu wpisać i po co?

Jeśli odpowiedź brzmi „nie”, popraw tekst etykiety lub dodaj uzupełniającą instrukcję (aria-describedby).

Najważniejsze dobre praktyki (checklista)

Poniższa lista podsumowuje kluczowe zasady pracy z etykietami i ARIA w formularzach:

  • etykieta dla każdego pola<label> powiązany przez for/id to podstawa;
  • placeholder ≠ etykieta – nigdy nie używaj placeholder jako jedynej etykiety;
  • grupowanie pól – powiązane pola umieszczaj w <fieldset> z opisującym <legend>;
  • oznaczanie pól wymaganych – stosuj oznaczenie wizualne (np. *) i programistyczne (required, ewentualnie aria-required);
  • dodatkowe instrukcje i błędy – przekazuj przez aria-describedby, zamiast zastępować etykietę;
  • rozsądne użycie aria-label – tylko gdy brak widocznego tekstu i nie można użyć label (np. przycisk-ikona);
  • bez dublowania nazw – unikaj jednoczesnego label i aria-label z różnymi treściami (ARIA nadpisuje naturalną nazwę);
  • preferuj aria-labelledby – gdy możesz oprzeć nazwę na istniejącym, widocznym tekście;
  • testuj w praktyce – weryfikuj formularze narzędziami automatycznymi i czytnikami ekranu, słuchając realnie odczytywanych etykiet;
  • jasność i zwięzłość – projektuj etykiety krótkie, opisowe i zgodne z WCAG 2.4.6.

Stosując te zasady, tworzysz formularze zgodne z WCAG i – co ważniejsze – realnie użyteczne dla osób korzystających z czytników ekranu, sterowania głosem i innych technologii asystujących.