Lekcja 19

Formularze: inputy, labelki, błędy i stany walidacji

Formularze są miejscem, w którym użytkownik przekazuje dane aplikacji. Dobry CSS nie tylko „upiększa” pola, ale pomaga rozumieć, co wpisać, gdzie jest fokus, co jest wymagane i jak naprawić błąd.

Cele lekcji

Po ukończeniu tej lekcji będziesz umieć:

  • budować semantyczne pola formularza z powiązanymi labelkami,
  • stylować inputy bez utraty dostępności,
  • projektować czytelny stan :focus-visible,
  • odróżniać tekst pomocniczy od komunikatu błędu,
  • stosować aria-describedby i aria-invalid,
  • rozumieć różnicę między disabled i readonly.

Kontekst praktyczny

Formularze pojawiają się w logowaniu, rejestracji, checkoutach, filtrach, wyszukiwarkach, ustawieniach konta i panelach administracyjnych. Są mało widowiskowe, ale bardzo wrażliwe na błędy UX: słaby label, niewidoczny fokus albo komunikat błędu bez kontekstu potrafią zatrzymać użytkownika.

W praktyce: formularz projektuj jak rozmowę. Label mówi, o co prosisz, hint dopowiada zasady, a błąd wyjaśnia, jak wrócić na właściwą ścieżkę.

HTML przed CSS

Zanim napiszesz style, upewnij się, że pole ma właściwy typ, labelkę i atrybuty pomagające przeglądarce. CSS nie naprawi formularza, który od początku jest zbudowany z anonimowych pól bez opisu.

<label for="email">Adres e-mail</label>
<input id="email" name="email" type="email" autocomplete="email" required>

type, autocomplete i required poprawiają klawiatury mobilne, autouzupełnianie i natywną walidację. To część jakości formularza, nie dodatek.

Labelki i opisy

Label powinien być widoczny i jednoznaczny. Placeholder może podpowiadać format, ale nie powinien zastępować labelki, bo znika po wpisaniu tekstu i bywa słabszy kontrastowo.

<div class="field">
  <label class="field__label" for="password">Hasło</label>
  <input
    class="field__control"
    id="password"
    name="password"
    type="password"
    aria-describedby="password-hint"
  >
  <p class="field__hint" id="password-hint">
    Minimum 8 znaków, w tym jedna cyfra.
  </p>
</div>
Zapamiętaj: for w labelce musi wskazywać na id pola. Dzięki temu kliknięcie labelki ustawia fokus w polu.

Bazowy styl pola

Kontrolka formularza powinna mieć pełną szerokość kontenera, wystarczający rozmiar dotykowy, czytelne obramowanie i odziedziczoną typografię.

.field__control {
  width: 100%;
  min-height: 2.85rem;
  padding: .75rem .9rem;
  border: 1px solid #cbd5e1;
  border-radius: .75rem;
  background: #fff;
  color: #0f172a;
  font: inherit;
}

font: inherit jest ważne także w formularzach, bo inputy, selecty i buttony mogą mieć domyślne style niezależne od reszty strony.

Focus i czytelność

Stan fokusu powinien być widoczny bez zgadywania. W formularzu użytkownik często przechodzi klawiaturą przez wiele pól, więc fokus musi jasno pokazywać aktualne miejsce pracy.

.field__control:focus-visible {
  border-color: #2563eb;
  outline: 3px solid #bfdbfe;
  outline-offset: 2px;
}

Dobry fokus nie polega wyłącznie na zmianie koloru ramki. Dodatkowy outline jest odporniejszy na różne tła i błędy kontrastu.

Stany walidacji

Stan błędu powinien wskazać pole, pokazać komunikat i wyjaśnić, co poprawić. Sam czerwony border to za mało, bo część użytkowników nie rozróżnia kolorów albo korzysta z czytnika ekranu.

<input
  class="field__control field__control--invalid"
  id="email"
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
>
<p class="field__error" id="email-error">
  Wpisz adres w formacie nazwa@domena.pl.
</p>
  • aria-invalid="true" informuje technologię asystującą, że pole ma błąd.
  • aria-describedby łączy pole z komunikatem błędu albo tekstem pomocniczym.
  • Komunikat powinien mówić, jak naprawić problem, nie tylko że „pole jest błędne”.

Disabled i readonly

disabled oznacza, że pola nie można edytować ani wysłać z formularzem. readonly pozwala odczytać i zaznaczyć wartość, ale blokuje edycję. Wizualnie oba stany powinny być spokojniejsze niż zwykłe pole, ale nie mogą wyglądać jak błąd.

.field__control:disabled {
  background: #e2e8f0;
  color: #64748b;
  cursor: not-allowed;
}

.field__control[readonly] {
  background: #f8fafc;
}

Jeżeli pole jest niedostępne, pokaż powód w tekście pomocniczym. Użytkownik nie powinien zgadywać, co odblokuje edycję.

Demo formularza

Demo pokazuje pełne pole z hintem, stan błędu, stan poprawny oraz pole niedostępne. Zwróć uwagę, że każdy komunikat jest widoczny tekstowo, a nie tylko przez kolor ramki.

Użyj danych, które mają pojawić się na certyfikacie.

Wpisz pełny adres, na przykład anna@example.com.

Wybrano plan zgodny z aktualnym modułem.

To pole odblokuje się po utworzeniu konta.

Przejdź do ćwiczenia

Najczęstsze błędy początkujących

  • Ukrywanie labelki i poleganie wyłącznie na placeholderze.
  • Usuwanie fokusu z pól formularza bez widocznej alternatywy.
  • Pokazywanie błędu tylko kolorem ramki, bez komunikatu tekstowego.
  • Brak powiązania komunikatu błędu z polem przez aria-describedby.
  • Nadużywanie disabled, gdy wystarczy wyjaśnić, co użytkownik ma zrobić.
  • Zbyt małe pola i przyciski na ekranach dotykowych.

Mini zadanie

Zbuduj prosty formularz zapisu do newslettera.

  • Dodaj pole e-mail z widoczną labelką.
  • Dodaj tekst pomocniczy pod polem.
  • Przygotuj wariant błędu z aria-invalid i komunikatem tekstowym.
  • Dodaj stan :focus-visible dla inputa i przycisku.
  • Sprawdź, czy formularz da się przejść klawiaturą i czy nie ma poziomego overflow na 390px.

Checklista

  • Każde pole ma widoczną labelkę powiązaną przez for i id.
  • Placeholder nie zastępuje labelki.
  • Inputy mają font: inherit, pełną szerokość i wygodny rozmiar dotykowy.
  • Fokus jest wyraźny i widoczny na jasnym tle.
  • Błędy mają komunikat tekstowy, nie tylko czerwony kolor.
  • Rozumiem różnicę między disabled i readonly.

Podsumowanie lekcji

Dostępny formularz zaczyna się od dobrego HTML: właściwego typu pola, widocznej labelki i opisów. CSS powinien wzmacniać semantykę przez czytelny fokus, jasne komunikaty błędów, spokojne stany niedostępne i stabilny układ na małych ekranach.

Kolejny krok roadmapy to karty, listy i sekcje treści.

Przejdź do Lekcji 20