Kurs HTML i CSS · Moduł 10: Dostępność (a11y)

Dostępne formularze

5 min czytania
W tej lekcji7

Wyobraź sobie, że wypełniasz formularz rezerwacji z zamkniętymi oczami, a czytnik ekranowy przy każdym polu mówi tylko "pole edycji, puste". Nie wiesz, czy wpisać imię, e-mail, czy datę wizyty. Tak brzmi formularz bez etykiet. Formularze to jedne z najważniejszych elementów interaktywnych na stronach internetowych. Tak jak skrybowie w Starożytnym Egipcie musieli wypełniać precyzyjne dokumenty papirusowe, tak użytkownicy muszą być prowadzeni przez formularze w sposób jasny i intuicyjny - szczególnie ci, którzy korzystają z technologii wspomagających.

Etykiety (Labels)

Każde pole formularza musi mieć powiązaną etykietę. To jest absolutny fundament dostępności formularzy - etykieta to napis na pieczęci, który mówi, co trzeba wpisać.

Jawne powiązanie (explicit)

Najpewniejszy sposób to połączenie atrybutu for etykiety z atrybutem id pola. Wartości muszą być identyczne, a id - unikalne na całej stronie:

1<!-- Najlepsza metoda - for/id -->
2<label for="name">Imię i nazwisko:</label>
3<input type="text" id="name" name="name">
4
5<!-- Czytnik ekranowy powie: "Imię i nazwisko, pole tekstowe" -->

Czytnik odczyta etykietę razem z typem pola, a kliknięcie w napis ustawi kursor w polu - większy cel przyda się każdemu, zwłaszcza na telefonie. Samo pole nie zmieniło wyglądu ani działania, zyskało tylko nazwę.

Niejawne powiązanie (implicit)

Drugi sposób to umieszczenie pola wewnątrz elementu <label> - wtedy atrybuty for i id nie są potrzebne:

1<!-- Input wewnątrz label -->
2<label>
3    Adres e-mail:
4    <input type="email" name="email">
5</label>

To powiązanie też jest poprawne, ale jako domyślne polecam jawne for i id: działa niezależnie od tego, jak ułożysz elementy w HTML, i łatwo je sprawdzić w DevTools.

Czego NIGDY nie robić

Dwa błędy widać na wielu stronach, choć oba wyglądają zupełnie niewinnie:

1<!-- ŹLE - brak etykiety -->
2<input type="text" placeholder="Wpisz imię">
3<!-- Placeholder ZNIKA po wpisaniu tekstu i NIE jest etykietą! -->
4
5<!-- ŹLE - tekst obok, ale niepowiązany -->
6<span>Imię:</span>
7<input type="text">
8<!-- Czytnik nie wie, że "Imię:" jest etykietą tego pola -->

Placeholder znika przy pierwszej literze, więc użytkownik traci podpowiedź w połowie pracy, a tekst w <span> jest dla czytnika zwykłym napisem, z niczym niepołączonym.

Fieldset i Legend

Grupuj powiązane pola za pomocą <fieldset> i <legend>. Legenda to tytuł całej grupy, który czytnik dołączy do pól w środku:

1<fieldset>
2    <legend>Dane osobowe</legend>
3
4    <label for="first-name">Imię:</label>
5    <input type="text" id="first-name" name="firstName">
6
7    <label for="last-name">Nazwisko:</label>
8    <input type="text" id="last-name" name="lastName">
9</fieldset>
10
11<fieldset>
12    <legend>Wybierz wystawę:</legend>
13
14    <label>
15        <input type="radio" name="exhibition" value="pharaohs">
16        Skarby faraonów
17    </label>
18
19    <label>
20        <input type="radio" name="exhibition" value="mummies">
21        Mumie królewskie
22    </label>
23
24    <label>
25        <input type="radio" name="exhibition" value="jewelry">
26        Biżuteria staroegipska
27    </label>
28</fieldset>

<fieldset> jest szczególnie ważny dla grup radio buttons i checkboxów - bez niego czytnik nie zna kontekstu pytania. Użytkownik usłyszałby "Skarby faraonów, przycisk opcji", nie wiedząc, że to odpowiedź na pytanie o wystawę. Wspólny atrybut name sprawia z kolei, że zaznaczyć można tylko jedną opcję.

Komunikaty o błędach

Obsługa błędów musi być dostępna - użytkownik musi wiedzieć, że wystąpił błąd i jak go naprawić.

Błędy inline

Komunikat przy polu łączysz z nim trzema atrybutami ARIA, które znasz już z lekcji o ARIA:

1<div class="form-group">
2    <label for="email">Adres e-mail (wymagany):</label>
3    <input type="email" id="email" name="email"
4           aria-required="true"
5           aria-invalid="true"
6           aria-describedby="email-error">
7    <p id="email-error" class="error" role="alert">
8        Podaj poprawny adres e-mail, np. jan@example.com
9    </p>
10</div>

aria-invalid="true" oznacza pole jako błędne, a aria-describedby wskazuje id komunikatu, więc czytnik odczyta go zaraz po etykiecie. role="alert" sprawia, że komunikat zostanie ogłoszony od razu, gdy pojawi się na stronie. Zwróć uwagę na treść: mówi, jak naprawić błąd, a nie tylko, że coś jest źle.

Podsumowanie błędów

W dłuższym formularzu dodaj na górze podsumowanie, w którym każdy błąd jest linkiem prowadzącym do pola:

1<div role="alert" aria-labelledby="error-summary-title">
2    <h2 id="error-summary-title">
3        Formularz zawiera 2 błędy:
4    </h2>
5    <ul>
6        <li>
7            <a href="#email">Pole e-mail - podaj poprawny adres</a>
8        </li>
9        <li>
10            <a href="#phone">Pole telefon - wymagane minimum 9 cyfr</a>
11        </li>
12    </ul>
13</div>

Kliknięcie linku #email przeniesie użytkownika prosto do błędnego pola, więc nie musi go szukać Tabem w całym formularzu.

Wskazówki i instrukcje

Wymagania stawiane polu pokaż, zanim użytkownik popełni błąd. Ten sam aria-describedby połączy pole z instrukcją:

1<label for="password">Hasło:</label>
2<input type="password" id="password" name="password"
3       aria-describedby="password-requirements">
4<div id="password-requirements">
5    <p>Hasło musi zawierać:</p>
6    <ul>
7        <li>Minimum 8 znaków</li>
8        <li>Wielką literę</li>
9        <li>Cyfrę</li>
10        <li>Znak specjalny</li>
11    </ul>
12</div>

Czytnik odczyta wymagania, gdy tylko focus trafi do pola hasła, więc osoba widząca i niewidoma dostają tę samą informację w tym samym momencie.

Autocomplete

Atrybut autocomplete pomaga przeglądarkom i technologiom wspomagającym automatycznie wypełnić formularz:

1<form>
2    <label for="full-name">Pełne imię i nazwisko:</label>
3    <input type="text" id="full-name" name="name"
4           autocomplete="name">
5
6    <label for="user-email">E-mail:</label>
7    <input type="email" id="user-email" name="email"
8           autocomplete="email">
9
10    <label for="user-tel">Telefon:</label>
11    <input type="tel" id="user-tel" name="phone"
12           autocomplete="tel">
13</form>

Wartości takie jak name, email czy tel pochodzą ze standardu HTML. WCAG 2.1 ma nawet osobne kryterium (1.3.5, poziom AA), które wymaga oznaczania w ten sposób pól z danymi użytkownika - osoby z niepełnosprawnością ruchową oszczędzają dzięki temu mnóstwo pisania.

Kompletny przykład formularza

Na koniec wszystkie elementy razem - formularz rezerwacji wizyty w muzeum:

1<form aria-labelledby="form-title" novalidate>
2    <h2 id="form-title">Rezerwacja wizyty w muzeum</h2>
3
4    <fieldset>
5        <legend>Dane osobowe</legend>
6
7        <div class="form-group">
8            <label for="visitor-name">Imię i nazwisko:</label>
9            <input type="text" id="visitor-name"
10                   autocomplete="name"
11                   aria-required="true">
12        </div>
13
14        <div class="form-group">
15            <label for="visitor-email">E-mail:</label>
16            <input type="email" id="visitor-email"
17                   autocomplete="email"
18                   aria-required="true"
19                   aria-describedby="email-hint">
20            <p id="email-hint" class="hint">
21                Na ten adres wyślemy potwierdzenie.
22            </p>
23        </div>
24    </fieldset>
25
26    <fieldset>
27        <legend>Szczegóły wizyty</legend>
28
29        <div class="form-group">
30            <label for="visit-date">Data wizyty:</label>
31            <input type="date" id="visit-date"
32                   aria-required="true">
33        </div>
34
35        <div class="form-group">
36            <label for="visitors-count">Liczba osób:</label>
37            <input type="number" id="visitors-count"
38                   min="1" max="20" value="1">
39        </div>
40    </fieldset>
41
42    <button type="submit">Zarezerwuj wizytę</button>
43</form>

Zwróć uwagę na novalidate przy <form>: wyłącza wbudowane dymki walidacji przeglądarki, bo pola sprawdza własny skrypt strony, a aria-required="true" mówi czytnikowi, które pola są wymagane. Jeśli nie piszesz własnej walidacji, użyj po prostu atrybutu required - przeglądarka zablokuje wysłanie pustego pola, a czytnik i tak ogłosi je jako wymagane.

Dostępne formularze to jak dobrze zaprojektowany dokument papirusowy - jasne instrukcje, czytelne pola i natychmiastowa informacja zwrotna pomagają każdemu użytkownikowi pomyślnie ukończyć wypełnianie. W lekcji o testowaniu sprawdzisz taki formularz czytnikiem ekranowym.

Praktyka

W edytorze poniżej znajdziesz formularz rezerwacji. Przejdź go Tabem i sprawdź, czy każde pole ma etykietę, a grupa opcji - swoją legendę:

Kod do tej lekcji: index.html
1<!DOCTYPE html>
2<html lang="pl">
3<head>
4    <meta charset="UTF-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1.0">
6    <title>Dostepne formularze</title>
7    <link rel="stylesheet" href="style.css">
8</head>
9<body>
10    <h1>Formularz rezerwacji wizyty w muzeum</h1>
11
12    <form aria-labelledby="form-title" novalidate>
13        <h2 id="form-title">Zarezerwuj wizyte</h2>
14
15        <fieldset>
16            <legend>Dane osobowe</legend>
17
18            <div class="form-group">
19                <label for="visitor-name">
20                    Imie i nazwisko:
21                    <span class="required" aria-hidden="true">*</span>
22                </label>
23                <input type="text" id="visitor-name"
24                       name="name"
25                       autocomplete="name"
26                       aria-required="true">
27            </div>
28
29            <div class="form-group">
30                <label for="visitor-email">
31                    Adres e-mail:
32                    <span class="required" aria-hidden="true">*</span>
33                </label>
34                <input type="email" id="visitor-email"
35                       name="email"
36                       autocomplete="email"
37                       aria-required="true"
38                       aria-describedby="email-hint">
39                <p id="email-hint" class="hint">
40                    Na ten adres wyslemy potwierdzenie rezerwacji.
41                </p>
42            </div>
43
44            <div class="form-group">
45                <label for="visitor-tel">Telefon:</label>
46                <input type="tel" id="visitor-tel"
47                       name="phone"
48                       autocomplete="tel">
49            </div>
50        </fieldset>
51
52        <fieldset>
53            <legend>Wybierz wystawe</legend>
54
55            <label class="radio-label">
56                <input type="radio" name="exhibition"
57                       value="tutankhamun">
58                Skarby Tutanchamona
59            </label>
60
61            <label class="radio-label">
62                <input type="radio" name="exhibition"
63                       value="mummies">
64                Mumie krolewskie
65            </label>
66
67            <label class="radio-label">
68                <input type="radio" name="exhibition"
69                       value="jewelry">
70                Bizuteria staroegipska
71            </label>
72        </fieldset>
73
74        <fieldset>
75            <legend>Szczegoly wizyty</legend>
76
77            <div class="form-group">
78                <label for="visit-date">
79                    Data wizyty:
80                    <span class="required" aria-hidden="true">*</span>
81                </label>
82                <input type="date" id="visit-date"
83                       name="date"
84                       aria-required="true">
85            </div>
86
87            <div class="form-group">
88                <label for="visitors-count">Liczba osob:</label>
89                <input type="number" id="visitors-count"
90                       name="count"
91                       min="1" max="20" value="1">
92            </div>
93
94            <div class="form-group">
95                <label>
96                    <input type="checkbox" name="guide">
97                    Chce przewodnika po wystawie
98                </label>
99            </div>
100        </fieldset>
101
102        <p class="required-info">
103            <span class="required" aria-hidden="true">*</span>
104            Pola wymagane
105        </p>
106
107        <button type="submit">Zarezerwuj wizyte</button>
108    </form>
109</body>
110</html>

Pamiętaj: każde pole to pieczęć na papirusie - bez podpisu w <label> nikt nie wie, co na niej odcisnąć.

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Kiedy należy używać elementów <fieldset> i <legend> w formularzach?

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż poprawne powiązanie label z inputem:

  • Edytor kodu

    Stwórz dostępny formularz rezerwacji z fieldset/legend, label for/id, aria-required i aria-describedby. Dodaj grupę radio z fieldset/legend.

  • Klikanie w kolejności

    Ułóż poprawną strukturę fieldset z legend dla grupy radio:

Przydatne artykuły