Встроенная валидация форм

Браузер умеет проверять форму сам — без единой строчки JavaScript. Разберём, как им управлять.

Встроенная (constraint) валидация — это набор HTML-атрибутов (required, pattern, min/max и др.), по которым браузер до отправки проверяет поля и сам показывает сообщения об ошибках.

Раньше «проверить форму на клиенте» означало писать JavaScript: ловить событие, бегать по полям, рисовать ошибки. Современный HTML делает большую часть этого декларативно. Вы описываете правила атрибутами — браузер берёт на себя проверку, блокировку отправки и подсказки пользователю.

Зачем это знать на практике

Встроенная валидация — первая линия обороны и удобства:

  • Мгновенная обратная связь. Пользователь узнаёт об ошибке сразу, не дожидаясь ответа сервера.
  • Меньше кода. Простые правила («обязательно», «не короче 8 символов», «похоже на e-mail») задаются атрибутами, без скриптов.
  • Доступность из коробки. Сообщения браузера озвучиваются скринридерами, привязка к полю корректна.

Важно: клиентская проверка — для удобства, а не для безопасности. Её легко обойти. Сервер обязан проверять данные ещё раз — это правило без исключений.

Базовые атрибуты ограничений

Каждый атрибут описывает одно правило. Их комбинируют на одном поле.

АтрибутЧто проверяет
requiredПоле не должно быть пустым.
minlength / maxlengthМинимум/максимум символов в тексте.
min / maxГраницы для числа или даты.
patternЗначение должно совпасть с регулярным выражением.
type="email" / type="url"Встроенная проверка формата адреса/ссылки.
<form>
  <label>Имя
    <input type="text" name="name" required minlength="2" maxlength="40">
  </label>

  <label>Возраст
    <input type="number" name="age" required min="14" max="120">
  </label>

  <label>Почта
    <input type="email" name="email" required>
  </label>

  <button>Отправить</button>
</form>

Если нажать «Отправить» с пустым обязательным полем, браузер не даст форме уйти и покажет всплывающую подсказку у первого проблемного поля. Числовое поле проверит границы, e-mail — формат.

pattern: проверка регулярным выражением

Атрибут pattern задаёт регулярное выражение, которому должно целиком соответствовать значение (якоря по краям подразумеваются автоматически). Это способ описать «свой формат»: индекс, артикул, логин.

<label>Индекс (6 цифр)
  <input type="text" name="zip" pattern="[0-9]{6}"
         title="Шесть цифр, например 101000">
</label>

<label>Логин (латиница, цифры, _ )
  <input type="text" name="login" pattern="[a-zA-Z0-9_]{3,16}">
</label>

Два совета по pattern. Первое: добавляйте title — его текст браузер покажет как подсказку, что именно ждут. Второе: pattern не делает поле обязательным. Пустое значение проходит проверку шаблоном — чтобы запретить пустоту, добавьте required.

Псевдоклассы :valid и :invalid

Состояние проверки доступно в CSS — можно подсветить корректные и ошибочные поля без JavaScript:

input:invalid {
  border-color: #d33;
}
input:valid {
  border-color: #2a2;
}
/* подсветим только после взаимодействия */
input:user-invalid {
  background: #fff5f5;
}

Тонкость UX: :invalid срабатывает сразу, даже на пустом обязательном поле, которое пользователь ещё не трогал — красить его красным с порога неприятно. Псевдокласс :user-invalid (и его «родственник» — поведение по событию) применяется только после того, как пользователь повзаимодействовал с полем или попытался отправить форму. Это и есть «правильный момент» для показа ошибки.

Отключение проверки: novalidate и formnovalidate

Иногда встроенная проверка мешает: на черновике формы, на кнопке «Сохранить как черновик», при собственной JS-валидации. Её выключают:

<!-- выключить проверку для всей формы -->
<form novalidate>
  ...
  <!-- ...но эта кнопка отправит без проверки точечно -->
  <button type="submit" formnovalidate>Сохранить черновик</button>
  <button type="submit">Опубликовать</button>
</form>

novalidate на <form> отключает проверку целиком; formnovalidate на конкретной кнопке — только при отправке этой кнопкой. Удобно для сценария «сохранить наполовину заполненное».

Сочетание атрибутов и читаемые сообщения

Сила встроенной валидации — в комбинировании простых правил на одном поле. Они работают как логическое «И»: значение должно удовлетворять всем ограничениям сразу. Поле пароля «обязательно, от 8 до 64 символов, хотя бы одна цифра» собирается так:

<label>Пароль
  <input type="password" name="pwd"
         required minlength="8" maxlength="64"
         pattern=".*[0-9].*"
         title="Минимум 8 символов и хотя бы одна цифра">
</label>

Здесь required запрещает пустоту, minlength/maxlength ограничивают длину, а pattern требует наличия цифры. Если нарушено несколько правил, браузер сообщит о первом нарушенном. Текст в title особенно важен для pattern: стандартное сообщение «Введите значение в требуемом формате» бесполезно, а ваше — объясняет, чего именно не хватает. Хорошая подсказка снижает раздражение сильнее, чем любое визуальное оформление ошибки.

Как это работает под капотом

За каждым полем стоит объект состояния валидности (ValidityState) с булевыми флагами: valueMissing (нарушен required), tooShort/tooLong, rangeUnderflow/rangeOverflow, patternMismatch, typeMismatch (плохой e-mail/url). Когда пользователь жмёт submit, браузер опрашивает все поля; если хоть одно невалидно, отправка отменяется, фокус и подсказка переходят к первому проблемному.

Текст подсказки по умолчанию формирует сам браузер (и переводит на язык интерфейса). Его можно заменить из JavaScript методом setCustomValidity:

<label>Пароль
  <input id="pwd" type="password" minlength="8" required>
</label>
<!-- сам код навешивается в JS-файле, без inline-обработчиков -->

В скрипте: если значение не подходит — input.setCustomValidity("Минимум 8 символов"); когда исправили — input.setCustomValidity("") (пустая строка снимает ошибку). Непустое сообщение делает поле невалидным и показывается вместо стандартного. Это нужно нечасто — для нетривиальных правил вроде «пароли должны совпадать».

Частые ошибки

  • Считать клиентскую проверку защитой. Её обходят за секунды (DevTools, прямой запрос). Сервер обязан валидировать данные заново.
  • Забывать required рядом с pattern. Пустое поле проходит pattern. Если пустота недопустима — нужен и required.
  • Красить :invalid с самого начала. Пустые обязательные поля сразу краснеют и пугают. Используйте :user-invalid или показывайте ошибку после первого взаимодействия.
  • Путать maxlength и max. maxlength — про число символов в тексте; max — про значение числа или даты.
  • Не снимать setCustomValidity. Если забыть вызвать его с пустой строкой после исправления, поле останется «вечно невалидным».

Итоги

  • Браузер проверяет форму до отправки по атрибутам required, minlength/maxlength, min/max, pattern, типам email/url.
  • pattern сверяет значение с регулярным выражением целиком; добавляйте title для подсказки и required, если пустота недопустима.
  • Псевдоклассы :valid/:invalid дают подсветку из CSS; :user-invalid срабатывает только после взаимодействия — это правильный момент для ошибки.
  • novalidate на форме и formnovalidate на кнопке отключают встроенную проверку (например, для черновика).
  • setCustomValidity("текст") задаёт своё сообщение, setCustomValidity("") снимает ошибку. Клиентская проверка — не замена серверной.
Проверьте себя
1. Поле имеет pattern="[0-9]{6}", но не имеет required. Что произойдёт при отправке пустого поля?
AФорма отправится: пустое значение проходит проверку pattern
BБраузер заблокирует отправку, потому что не введены 6 цифр
CБраузер подставит шесть нулей автоматически
DВозникнет ошибка синтаксиса регулярного выражения
2. Почему для подсветки ошибочных полей часто предпочитают :user-invalid вместо :invalid?
A:user-invalid срабатывает только после взаимодействия пользователя, а :invalid — сразу, даже на нетронутом пустом поле
B:invalid не поддерживается ни одним современным браузером
C:user-invalid умеет менять текст сообщения об ошибке
Dмежду ними нет разницы, это синонимы
3. Что делает вызов input.setCustomValidity("") (с пустой строкой) в JavaScript?
AСнимает кастомное сообщение и снова делает поле валидным
BУстанавливает сообщение из пробела и блокирует форму
CПолностью отключает встроенную валидацию формы
DОчищает введённое пользователем значение