Перейти к основному содержимому

Валидация форм в JavaScript

Разработчику Архитектору

Два уровня проверки

Браузер умеет проверять поля до отправки на сервер:

  1. HTMLrequired, minlength, type="email", type="date", pattern, min / max (см. типы <input> и справочник HTML).
  2. JavaScript — Constraint Validation API — читать состояние поля, показывать свои сообщения, блокировать submit.

Серверная проверка остаётся обязательной: клиентский код можно обойти. Виды проверок и примеры — Проверка и валидация.

Связь: события форм, работа с DOM, регулярные выражения для сложных pattern, готовые шаблоны для pattern, чтение и загрузка файлов.


Свойства полей по типу <input>

Разметка и HTML-атрибуты — в типах <input> и справочнике HTML. Ниже — что читает и меняет JavaScript у каждого типа поля.

Общие свойства элементов формы

Свойство / методНазначение
valueтекущее значение поля
defaultValueзначение из HTML при загрузке страницы
required, disabled, readOnlyобязательность, блокировка, только чтение
formссылка на родительский <form> (поле может быть вне тега, если указан атрибут form="id")
typeтип элемента (text, checkbox, …)
autofocusфокус при загрузке (только один такой элемент на странице)
validity, validationMessage, willValidateсостояние проверки — см. объект validity
focus(), blur(), select()фокус, снятие фокуса, выделение текста в поле

Таблица по типам <input>

typeКлючевые свойства и методы в JS
text, email, url, tel, search, passwordvalue, minLength, maxLength, placeholder, pattern, selectionStart / selectionEnd, select()
number, rangevalue, min, max, step, stepUp(n?), stepDown(n?)
date, time, datetime-local, month, weekvalue в формате ISO (2026-05-30, 14:30, …), min, max, step
colorvalue как #RRGGBB (например #ff0000)
checkboxchecked, defaultChecked, indeterminate (промежуточное состояние "частично выбрано")
radiochecked; группа полей с одним name — через document.querySelectorAll('input[name="gender"]')
filefiles (FileList), multiple, accept; у каждого File: name, size, type, lastModified
hiddenтолько value
submit, reset, button, imagevalue; у submit/image также formAction, formMethod, formEnctype
const qty = document.querySelector('#qty');
qty.stepUp(); // +step (по умолчанию 1)

const avatar = document.querySelector('#avatar');
for (const file of avatar.files) {
console.log(file.name, file.size);
}

const agree = document.querySelector('#agree');
agree.indeterminate = true; // «частично выбрано» в UI

textarea и select

ЭлементДополнительно в JS
textareavalue, rows, cols, wrap (soft / hard / off), spellcheck; Enter по умолчанию — новая строка, не submit
selectvalue — значение выбранного <option>; selectedIndex; options — коллекция пунктов; multiple — массив через selectedOptions

События полей — input (каждый символ), change (после commit — blur у text, сразу у select/checkbox), focus / blur, invalid — в событиях форм.


UX-практики валидации форм

Пользователь ожидает предсказуемую обратную связь:

  • сообщение об ошибке рядом с полем, а не одно общее "данные неверны";
  • фокус на первом невалидном поле после submit;
  • снятие подсказки после исправления без повторной отправки, где это уместно.

Constraint Validation API (ниже, объект validity) закрывает эти требования без тяжёлых UI-библиотек.


Объект validity

У каждого поля формы (HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement) есть свойство validity — набор флагов, почему значение не прошло проверку:

СвойствоКогда true
valueMissingпустое обязательное поле
typeMismatchне подходит type (email, url…)
patternMismatchне совпало с pattern
tooShort / tooLongдлина вне minlength / maxlength
rangeUnderflow / rangeOverflowчисло или дата вне min / max
stepMismatchне кратно step
badInputбраузер не может прочитать значение
customErrorвы вызвали setCustomValidity с непустой строкой
validвсе проверки пройдены
const email = document.querySelector('#email');

email.addEventListener('input', () => {
if (email.validity.typeMismatch) {
console.log('Нужен адрес вида user@example.com');
}
});

Разбор:

  • querySelector('#email') получает ссылку на поле по id и даёт доступ к его API валидации.
  • Событие input срабатывает на каждое изменение значения, поэтому проверка выполняется "на лету".
  • email.validity.typeMismatch проверяет, соответствует ли значение типу поля (type="email").
  • Такой обработчик подходит для моментальных подсказок без ожидания submit.
  • Подход уменьшает количество неудачных отправок формы и ускоряет исправление ошибок пользователем.

Основные методы

checkValidity()

Возвращает true, если поле (или форма) валидно. Не показывает встроенный "пузырь" браузера.

if (!form.checkValidity()) {
// подсветить ошибки своим UI
}

Разбор:

  • checkValidity() запускает все встроенные правила HTML для формы и возвращает логический результат.
  • Знак ! инвертирует условие, поэтому ветка выполняется только при наличии хотя бы одной ошибки.
  • Метод не показывает стандартные сообщения браузера, что удобно для полностью кастомного интерфейса.
  • В этом месте обычно включают подсветку полей, список ошибок и перевод фокуса на первое проблемное поле.

reportValidity()

Как checkValidity, но при false браузер показывает стандартное сообщение у первого невалидного поля (если не отключено CSS).

form.addEventListener('submit', (event) => {
if (!form.reportValidity()) {
event.preventDefault();
}
});

Разбор:

  • Обработчик submit перехватывает отправку в момент нажатия кнопки или вызова requestSubmit().
  • reportValidity() совмещает проверку и показ нативного сообщения у первого невалидного поля.
  • Если форма невалидна, preventDefault() отменяет отправку данных на сервер.
  • Такая конструкция сохраняет нативный UX браузера и требует минимум кода для базовой валидации.
  • Переменная event даёт контроль над поведением формы в текущем цикле событий.

setCustomValidity(message)

Задаёт свою причину ошибки. Пустая строка '' снимает customError.

const password = document.querySelector('#password');
const confirm = document.querySelector('#confirm');

function validatePair() {
if (confirm.value && confirm.value !== password.value) {
confirm.setCustomValidity('Пароли не совпадают');
} else {
confirm.setCustomValidity('');
}
}

password.addEventListener('input', validatePair);
confirm.addEventListener('input', validatePair);

Разбор:

  • Функция validatePair инкапсулирует правило "подтверждение равно паролю" в одном месте.
  • Условие confirm.value && confirm.value !== password.value проверяет только заполненное поле подтверждения.
  • setCustomValidity('Пароли не совпадают') устанавливает кастомную ошибку и переводит validity.customError в true.
  • setCustomValidity('') обязательно очищает ошибку после исправления данных.
  • Подписка на input у обоих полей даёт синхронную реакцию при изменении любой стороны пары.

Правило — после каждого изменения, влияющего на правило, снова вызывайте setCustomValidity('') или с новым текстом.


События

СобытиеКогда
invalidполе не прошло проверку при submit (можно preventDefault на кастомный UI)
inputзначение меняется — удобно снимать ошибку "на лету"
changeзначение зафиксировано (select, checkbox после выбора)
field.addEventListener('invalid', (event) => {
event.preventDefault(); // отключить стандартный bubble
showError(field, field.validationMessage);
});

Разбор:

  • Событие invalid срабатывает, когда конкретное поле не проходит проверку при отправке или вызове reportValidity.
  • preventDefault() отключает стандартный всплывающий bubble браузера, если вы рисуете свой UI.
  • field.validationMessage возвращает готовый текст ошибки, включая сообщения из setCustomValidity.
  • Вызов showError(...) выносит рендер ошибки в отдельную функцию и упрощает повторное использование.
  • Такой подход помогает сделать единый стиль сообщений для всех полей формы.

validationMessage — текст, который показал бы браузер (учитывает setCustomValidity).


Связка с CSS

В псевдоклассах CSS используют :user-invalid / :invalid для подсветки:

input:user-invalid {
border-color: #c0392b;
outline: 2px solid rgba(192, 57, 43, 0.3);
}

Разбор:

  • Псевдокласс :user-invalid применяет стиль только после взаимодействия пользователя с полем.
  • border-color задаёт заметный цвет рамки для быстрого визуального обнаружения ошибки.
  • outline добавляет внешний контур, который лучше читается на разных фоновых цветах.
  • Такая комбинация улучшает доступность интерфейса и поддерживает понятный визуальный фидбек.

:user-invalid срабатывает после взаимодействия пользователя — меньше "красных полей" при первой загрузке страницы.


Пример — форма с единым блоком ошибок

<form id="signup" novalidate>
<!-- novalidate — только наш или смешанный сценарий; иначе двойные сообщения -->
<label>
Логин
<input name="login" required minlength="3" id="login">
</label>
<p id="login-error" hidden></p>
<button type="submit">Создать</button>
</form>

Разбор:

  • Атрибут novalidate отключает нативные всплывающие сообщения, чтобы полностью контролировать UX из JavaScript.
  • required и minlength="3" продолжают задавать правила, даже если визуализация ошибок кастомная.
  • id="login-error" создаёт отдельный узел для текстовой подсказки рядом с полем.
  • Кнопка type="submit" запускает стандартный цикл отправки формы и обработчик submit.
  • Такая структура даёт хороший баланс между семантикой HTML и гибкостью клиентской логики.

Код ITЗагрузка примера кода…

Разбор:

  • Обработчик input очищает старую ошибку и сразу прячет блок с сообщением после правки поля.
  • Проверка form.checkValidity() в submit даёт единый вход для всех ограничений HTML.
  • При ошибке код показывает конкретный текст validationMessage, снимает hidden и переводит фокус на проблемное поле.
  • return завершает обработчик, чтобы не выполнять отправку при невалидном состоянии.
  • Комментарий в конце указывает две стратегии отправки: через fetch или стандартный form.submit().

Атрибут novalidate на <form> отключает встроенный UI браузера — полезно, если сообщения рисуете вы сами. Без него можно вызывать reportValidity() точечно.


Асинхронная проверка (логин занят)

Встроенный API синхронный. Проверка "логин свободен на сервере" делается так:

  1. На submitpreventDefault.
  2. fetch на API.
  3. При конфликте — field.setCustomValidity('Логин занят') и field.reportValidity().
  4. При успехе — form.requestSubmit() или отправка данных.

Не вызывайте setCustomValidity с текстом до ответа сервера на каждый input без debounce — иначе лишние запросы.


Отправка данных — FormData

После успешной проверки форму обычно отправляют на сервер. FormData собирает пары "имя поля → значение" из разметки, в том числе файлы:

Код ITЗагрузка примера кода…

Разбор:

  • event.preventDefault() отменяет стандартную отправку, чтобы сначала выполнить клиентскую проверку и асинхронный fetch.
  • Блок if (!form.checkValidity()) с reportValidity() останавливает поток до сети, если локальные правила нарушены.
  • new FormData(form) собирает данные по name из формы и автоматически включает файлы.
  • body.append('source', 'web') добавляет техническое поле, которого нет в HTML.
  • В fetch не нужно вручную задавать Content-Type для FormData: браузер сам сформирует корректный multipart/form-data с boundary.
  • Проверка response.ok отделяет успешный HTTP-ответ от ошибок сервера и упрощает обработку исключений.

Полезные методы:

МетодНазначение
new FormData(form)Снимок всех полей формы с атрибутом name
append(name, value)Добавить или продублировать ключ
get(name) / getAll(name)Прочитать одно или все значения
delete(name)Убрать ключ перед отправкой
entries()Итерация для отладки или ручной сборки

Файлы: <input type="file" name="avatar"> попадает в FormData как File. Несколько файлов — getAll('avatar').

Если API ждёт JSON, а не multipart, соберите объект вручную:

const payload = Object.fromEntries(new FormData(form));
await fetch('/api/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
});

Разбор:

  • Object.fromEntries(new FormData(form)) превращает пары из формы в обычный объект JavaScript.
  • JSON.stringify(payload) сериализует объект в строку для передачи по HTTP как JSON.
  • Заголовок Content-Type: application/json сообщает серверу формат тела запроса.
  • Этот вариант удобен для API, которые ожидают JSON, но не подходит для бинарных файлов без дополнительной логики.
  • Код остаётся компактным и хорошо читаемым для типовых форм без upload-полей.

Для JSON файлы не сериализуются — для загрузки файлов оставляйте FormData или отдельный fetch с Blob. Отмена долгой отправки — AbortController; разбор ответа — в асинхронном программировании.


Практические правила

  • Дублируйте критичные правила на сервере.
  • Сообщения — рядом с полем, связь label + id, для скринридеров — aria-invalid="true" и aria-describedby на блок ошибки.
  • Не валидируйте скрытые поля (display: none) так же, как видимые — пользователь не может исправить.
  • Для масок (телефон) — inputmode, pattern или маска в JS; регулярные выражения — для pattern, не для всей логики формы.

Типовой сценарий — регистрация с API

Практический поток в продакшене обычно строят так:

  1. Пользователь вводит данные, браузер проверяет базовые правила (required, type, minlength).
  2. На submit скрипт запускает form.checkValidity().
  3. При локальной ошибке интерфейс показывает подсказки у полей и фокус переводится на первое проблемное поле.
  4. При локальном успехе код отправляет FormData через fetch.
  5. Сервер выполняет бизнес-валидацию (уникальность email, политика пароля, антиспам) и возвращает ответ.

Такой конвейер объединяет быстрый UX на клиенте и надёжную проверку на сервере. Разделение ролей особенно важно в формах оплаты и регистрации. Для транспортного слоя используйте материалы по HTTP и обработке API-ошибок. В React те же идеи — controlled inputs и onSubmit — см. галерею форм и валидации и 272.


Частые ошибки и исправления

ОшибкаЧто происходитКак исправить
Включён novalidate, но нет своего UI ошибокпользователь нажимает submit и не видит причину блокировкипоказывать сообщения рядом с полем и в общем блоке формы
Сообщение в setCustomValidity не очищаетсяполе остаётся "красным" после исправлениясбрасывать setCustomValidity('') на каждом релевантном input
Валидация только в браузерев API попадают некорректные или вредоносные данныедублировать правила на бэкенде и логировать причины отказов
Ошибки идут только цветом рамкичасть пользователей не понимает проблемудобавить текст ошибки и aria-describedby для доступности

Краткий итог

Constraint Validation API связывает HTML-атрибуты и скрипт — validity, checkValidity, setCustomValidity, reportValidity. Начните с нативных атрибутов, добавьте JS для парных полей и серверных правил, оформите ошибки через CSS и доступную разметку.