Валидация форм в JavaScript
Два уровня проверки
Браузер умеет проверять поля до отправки на сервер:
- HTML —
required,minlength,type="email",type="date",pattern,min/max(см. типы<input>и справочник HTML). - 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, password | value, minLength, maxLength, placeholder, pattern, selectionStart / selectionEnd, select() |
number, range | value, min, max, step, stepUp(n?), stepDown(n?) |
date, time, datetime-local, month, week | value в формате ISO (2026-05-30, 14:30, …), min, max, step |
color | value как #RRGGBB (например #ff0000) |
checkbox | checked, defaultChecked, indeterminate (промежуточное состояние "частично выбрано") |
radio | checked; группа полей с одним name — через document.querySelectorAll('input[name="gender"]') |
file | files (FileList), multiple, accept; у каждого File: name, size, type, lastModified |
hidden | только value |
submit, reset, button, image | value; у 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 |
|---|---|
textarea | value, rows, cols, wrap (soft / hard / off), spellcheck; Enter по умолчанию — новая строка, не submit |
select | value — значение выбранного <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 синхронный. Проверка "логин свободен на сервере" делается так:
- На
submit—preventDefault. fetchна API.- При конфликте —
field.setCustomValidity('Логин занят')иfield.reportValidity(). - При успехе —
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
Практический поток в продакшене обычно строят так:
- Пользователь вводит данные, браузер проверяет базовые правила (
required,type,minlength). - На
submitскрипт запускаетform.checkValidity(). - При локальной ошибке интерфейс показывает подсказки у полей и фокус переводится на первое проблемное поле.
- При локальном успехе код отправляет
FormDataчерезfetch. - Сервер выполняет бизнес-валидацию (уникальность 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 и доступную разметку.