Формы: вёрстка и валидация

Формы: структура и отправка, типы полей, связи label и id, HTML5-валидация, автозаполнение, группы полей и UX ошибок.

Уровень: начальный 12 разделов 2 часа

1. Форма и её отправка

Форма — главный способ общения сайта с пользователем и сервером: регистрация, заказ, поиск, комментарий. Разметка — элемент form с дочерними полями и кнопкой отправки. Без разметки скрипта форма всё равно передаёт данные классическим способом.

<form action="/api/order" method="post">
  <input type="text" name="name">
  <button type="submit">Отправить</button>
</form>
АтрибутРоль
actionадрес, на который уходят данные
methodget — данные в строке запроса, post — в теле запроса
novalidateотключить встроенную проверку браузера (проверяет JS)
autocompleteотключить/настроить подсказки браузера для всей формы
Поле без атрибута name не отправляется: это частая причина «форма не работает», когда сервер не видит часть данных. Проверяйте name у каждого поля.

2. label и связь с полем

Подпись поля (label) делает как минимум две вещи: ставит фокус в поле по клику на текст и даёт имя поля скринридеру. Связь обязательна — через for и id либо обёрткой поля внутри label.

<!-- вариант 1: связь по id -->
<label for="city">Город</label>
<input type="text" id="city" name="city">

<!-- вариант 2: обёртка -->
<label>
  Город
  <input type="text" name="city">
</label>
Дублировать подпись внутри поля тонким placeholder-ом не запрещено, но не делайте подпись только placeholder-ом. Для форм с перечнем правил валидации сверху добавляйте подсказку через title или отдельный текст с aria-describedby.

3. Типы полей

Атрибут type рассказывает браузеру, что вводит пользователь: телефон клавиатура на мобильных, даты — панель календаря, числа — валидация цифр. Использование правильных типов бесплатно даёт UX и половину валидации.

ТипНазначениеОсобенность
textкороткий текстбаза, без специальной проверки
emailэлектронная почтапроверка формата, подсказка @
telтелефонцифровая клавиатура на мобильных
urlадрес сайтапроверка протокола http/https
numberчислострелки, min/max/step
searchпоисккнопка «очистить», скрытие с клавиатуры
date / monthдатакалендарь в браузере
passwordпарольточки вместо букв
hiddenслужебная меткане видим, но отправляется
<input type="email" name="mail">
<input type="tel" name="phone" placeholder="+7 900 000-00-00">
<input type="url" name="site">
<input type="number" name="qty" min="1" max="99" step="1">
<input type="search" name="q">

4. Ограничения ввода

Чтобы пользователь не отправил мусор, сперва ограничивают возможное, а потом проверяют. Ограничения «на лету»: длина, диапазон, образец. Валидация образца — через регулярное выражение в pattern.

<input type="text" name="login" maxlength="20" pattern="[A-Za-z0-9_]{3,20}"
       title="3–20 знаков: латиница, цифры, подчёркивание" required>

<textarea name="message" maxlength="500"></textarea>
<span class="hint">0 / 500</span>

<input type="number" name="age" min="18" max="100">
pattern проверяет значение целиком, а не подстроку: выражение [A-Za-z0-9_]{3,20} заставляет длинные строки тоже укладываться в шаблон. Обрамлять его в ^…$ не обязательно — браузер делает это сам.

5. Валидация HTML5

Браузер проверяет поля с required, type и pattern при отправке и не даёт отправить невалидные данные, показывая сообщение у первого неверного поля. Это работает вообще без JavaScript — важно для устойчивости формы.

<form>
  <label for="mail">Почта</label>
  <input type="email" id="mail" name="mail" required>
  <button type="submit">Войти</button>
</form>

6. Автозаполнение

Браузер запоминает данные, которые пользователь вводил раньше, и предлагает их при следующей отправке. Чтобы подсказки работали предсказуемо и не заставляли думать, укажите каждому полю autocomplete.

<input type="text" name="name" autocomplete="name">
<input type="email" name="email" autocomplete="email">
<input type="tel" name="phone" autocomplete="tel">

<label for="d">Доставка</label>
<input type="text" id="d" name="address" autocomplete="street-address">
<input type="text" name="city" autocomplete="address-level2">
ЗначениеПоле
nameполное имя
emailэлектронная почта
telтелефон
street-addressадрес
postal-codeиндекс
cc-numberномер карты
current-password / new-passwordпароль: текущий / новый
one-time-codeкод из SMS

7. textarea и select

Многострочный текст — textarea, выбор из вариантов — select с option. У textarea нет value в разметке: текст — это содержимое тега. Варианты можно объединять в группы optgroup.

<label for="comment">Комментарий</label>
<textarea id="comment" name="comment" rows="4" maxlength="400"
          placeholder="Что хотели сказать?"></textarea>

<label for="city">Город</label>
<select id="city" name="city" required>
  <option value="" disabled selected>— выберите —</option>
  <optgroup label="Россия">
    <option value="msk">Москва</option>
    <option value="spb">Санкт-Петербург</option>
  </optgroup>
  <optgroup label="Беларусь">
    <option value="minsk">Минск</option>
  </optgroup>
</select>

8. Группы полей

Логически связанные поля объединяют в fieldset с итоговой подписью legend. Визуально — рамка, семантически — группа, о которой скринридер объявит («Адрес доставки»). Внутри группы радиокнопки ведут себя как переключатели одной группы.

<fieldset>
  <legend>Способ доставки</legend>
  <label><input type="radio" name="delivery" value="courier" checked> Курьер</label>
  <label><input type="radio" name="delivery" value="pickup"> Самовывоз</label>
  <label><input type="radio" name="delivery" value="post"> Почта</label>
</fieldset>
Обвязка label вокруг чекбокса или радиокнопки делает кликабельной всю строку подписи — удобнее, чем цель в 15 пикселей самого квадратика. Такая вложенность равноценна связке for/id.

9. Кнопки отправки

Кнопка в форме по умолчанию — submit: Enter в любом поле и клик отправляют форму. Поведение меняют типы кнопок: submit (отправить), reset (очистить) и button (обычная — только для JS).

<form>
  <input type="text" name="q">
  <button type="submit">Искать</button>
  <button type="button" class="clear">Очистить</button>
</form>
Надписи кнопок («Отправить», «Войти») — реальный текст, а не картинка. Такие кнопки читаются скринридером, а поиск («Отправить форму заказа») находит их.

10. Стилизация состояний

Состояниям полей соответствуют псевдоклассы: :valid, :invalid, :user-invalid, :required, :disabled, :focus-visible, :checked. Поле, которое пользователь ещё не трогал, не должно светиться красным :invalid — это давит и пугает. :user-invalid применяется только после взаимодействия.

input { font: inherit; padding: 8px 10px; border: 1px solid #9ca3af; border-radius: 6px; }

/* ошибка видна только после попытки ввода или отправки */
input:user-invalid { border-color: #dc2626; }
input:user-invalid:focus-visible { outline-color: #dc2626; }

input:focus-visible { outline: 3px solid #2563eb; outline-offset: 1px; }
input:disabled { background: #f3f4f6; color: #9ca3af; }

11. Отправка без перезагрузки

Классическая отправка перезагружает страницу. Для быстрых форм (комментарий, заказ из корзины) данные отправляют через fetch: сервер отвечает, страница обновляет состояние без перезагрузки.

const form = document.querySelector("#order-form");

form.addEventListener("submit", async (event) => {
  event.preventDefault(); // отключаем перезагрузку

  const data = new FormData(form);
  const response = await fetch("/api/order", {
    method: "POST",
    body: data,
  });

  if (response.ok) {
    status.textContent = "Заказ принят!";
  } else {
    status.textContent = "Ошибка: " + response.status;
  }
});

12. Чек-лист формы

Перед публикацией пройдитесь по короткому списку. Он покрывает и удобство, и доступность, и надёжность доставки данных.

Самый честный тест формы: отправьте её с реальными данными и посмотрите во вкладке Network, что реально уехало на сервер. Иногда проблема не в поле, а в отсутствии name или в неверном action.

Что дальше

Курс завершён.

← Вернуться к курсам