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

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

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

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

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

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

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

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


Объект 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');
}
});

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

checkValidity()

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

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

reportValidity()

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

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

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);

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


События

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

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


Связка с CSS

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

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

: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>
const form = document.getElementById('signup');
const login = document.getElementById('login');
const loginError = document.getElementById('login-error');

login.addEventListener('input', () => {
login.setCustomValidity('');
loginError.hidden = true;
});

form.addEventListener('submit', (event) => {
if (!form.checkValidity()) {
event.preventDefault();
if (!login.validity.valid) {
loginError.textContent = login.validationMessage;
loginError.hidden = false;
login.focus();
}
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 собирает пары «имя поля → значение» из разметки, в том числе файлы:

const form = document.getElementById('signup');

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

const body = new FormData(form);
// Дополнительные поля, которых нет в HTML:
body.append('source', 'web');

const response = await fetch('/api/signup', {
method: 'POST',
body, // Content-Type с boundary выставит браузер сам
});

if (!response.ok) throw new Error('Ошибка сервера');
const data = await response.json();
console.log('Создан пользователь', data.id);
});

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

МетодНазначение
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),
});

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


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

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

Краткий итог

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


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).