Формы: вёрстка и валидация
Формы: структура и отправка, типы полей, связи label и id, HTML5-валидация, автозаполнение, группы полей и UX ошибок.
1. Форма и её отправка
Форма — главный способ общения сайта с пользователем и сервером: регистрация, заказ, поиск, комментарий. Разметка — элемент form с дочерними полями и кнопкой отправки. Без разметки скрипта форма всё равно передаёт данные классическим способом.
<form action="/api/order" method="post">
<input type="text" name="name">
<button type="submit">Отправить</button>
</form>| Атрибут | Роль |
|---|---|
| action | адрес, на который уходят данные |
| method | get — данные в строке запроса, post — в теле запроса |
| novalidate | отключить встроенную проверку браузера (проверяет JS) |
| autocomplete | отключить/настроить подсказки браузера для всей формы |
- для поиска и фильтров подходит method="get" (ссылка остаётся в истории);
- для регистрации, заказов и загрузки файлов — method="post";
- данные типа input передаются парами «имя=значение» по атрибуту name.
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>- у каждого поля есть label — в том числе у чекбоксов и радиокнопок;
- label без for и без обёртки бесполезна (не связана ни с чем);
- placeholder не заменяет label: после ввода текста подсказка пропадает;
- aria-label — крайняя мера, если видимой подписи нет по дизайну.
3. Типы полей
Атрибут type рассказывает браузеру, что вводит пользователь: телефон клавиатура на мобильных, даты — панель календаря, числа — валидация цифр. Использование правильных типов бесплатно даёт UX и половину валидации.
| Тип | Назначение | Особенность |
|---|---|---|
| text | короткий текст | база, без специальной проверки |
| электронная почта | проверка формата, подсказка @ | |
| 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">- maxlength ограничивает ввод, min/max — диапазон чисел и дат;
- pattern проверяет по регулярному выражению (работает для text, search, url, tel, email);
- title с описанием правила показывается в сообщении об ошибке браузера;
- required запрещает пустое поле.
[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>- сообщения браузерные, локализованы языком браузера, и их трудно стилизовать;
- novalidate на форме отключает проверку — если проверка идёт в JS и дизайн ошибок свой;
- псевдоклассы :valid / :invalid / :user-invalid дают стилизовать состояния полей;
- после отправки с ошибкой неправильное поле получает :invalid и фокус стоит на первой ошибке.
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 | полное имя |
| электронная почта | |
| tel | телефон |
| street-address | адрес |
| postal-code | индекс |
| cc-number | номер карты |
| current-password / new-password | пароль: текущий / новый |
| one-time-code | код из SMS |
- не отключайте автозаполнение (autocomplete="off") без причины;
- второй пароль («повторите пароль») — new-password, а не первый;
- код из SMS — one-time-code: на мобильных браузер подставит его из сообщения.
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>- textarea: атрибуты rows/cols, maxlength; отступы внутри тега попадают в значение — пишите текст вплотную к тегу;
- первый option — подсказка «выберите», value пустой, чтобы required срабатывал;
- для множественного выбора — select multiple (удерживайте Ctrl), но чаще используют чекбоксы;
- поиск по длинному списку удобнее делать полем с подсказками (input + datalist).
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>- радио одной группы — одинаковый name, выбрано не более одного;
- чекбоксы подходят, когда вариантов может быть несколько;
- чекбокс, отмеченный по умолчанию, избавляет от лишних кликов.
9. Кнопки отправки
Кнопка в форме по умолчанию — submit: Enter в любом поле и клик отправляют форму. Поведение меняют типы кнопок: submit (отправить), reset (очистить) и button (обычная — только для JS).
<form>
<input type="text" name="q">
<button type="submit">Искать</button>
<button type="button" class="clear">Очистить</button>
</form>- Enter в текстовом поле отправляет форму всегда;
- одиночное поле без кнопки тоже отправляется по Enter;
- при ошибке валидации браузер блокирует отправку и показывает сообщение у первого неверного поля;
- reset редки: случайный клик теряет введённые данные.
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; }- для ошибок не полагайтесь только на цвет — добавьте иконку и текст;
- :focus-visible рисует контур только для клавиатуры, не для мыши;
- disabled-поля не отправляются — пользователь потеряет неосторожно введённое.
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;
}
});- FormData собирает все поля формы по атрибутам name;
- для JSON-API вместо FormData собирают объект с теми же ключами;
- даже с JS оставьте рабочую классическую отправку: пользователь без JS — тоже клиент;
- после успешной отправки сообщите об этом скринридеру (aria-live).
12. Чек-лист формы
Перед публикацией пройдитесь по короткому списку. Он покрывает и удобство, и доступность, и надёжность доставки данных.
- у каждого поля есть label, связанный через for/id;
- поля имеют подходящие type и autocomplete;
- обязательные поля помечены required и визуально (звёздочкой или «обязательно»);
- ошибки видны текстом рядом с полем и связаны aria-describedby;
- submit работает по Enter, кнопка не «застревает» в неактивном состоянии;
- кириллица и эмодзи не ломаются при передаче (UTF-8 на сервере);
- сообщение об успехе не исчезает мгновенно.
Что дальше
Курс завершён.