Kurs HTML i CSS · Moduł 10: Dostępność (a11y)
Dostępne formularze
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. 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: