Валидация форм
HTML5 предоставляет встроенные атрибуты валидации (
required,pattern,min/max), а Constraint Validation API позволяет создавать кастомные проверки через JavaScript.
Зачем нужно
Валидация на клиенте -- первая линия защиты от неправильных данных. Она даёт мгновенную обратную связь пользователю без обращения к серверу. Но серверная валидация обязательна -- клиентскую можно обойти.
Где используется
- Все формы с обязательными полями
- Регистрация/логин
- Оформление заказа
- Любой пользовательский ввод
Предпосылки
HTML-атрибуты валидации
required -- обязательное поле
<label for="name">Имя (обязательно):</label>
<input type="text" id="name" name="name" required>
<!-- Форма не отправится, если поле пустое -->
minlength / maxlength -- длина текста
<label for="pwd">Пароль (8-64 символов):</label>
<input type="password" id="pwd" name="password"
required minlength="8" maxlength="64">
<label for="bio">О себе (до 200 символов):</label>
<textarea id="bio" name="bio" maxlength="200"></textarea>
min / max -- диапазон чисел/дат
<!-- Число -->
<label for="age">Возраст (18-120):</label>
<input type="number" id="age" name="age" min="18" max="120">
<!-- Дата -->
<label for="date">Дата (не раньше сегодня):</label>
<input type="date" id="date" name="date" min="2026-04-06">
step -- шаг
<!-- Целые числа -->
<input type="number" name="qty" min="1" max="100" step="1">
<!-- С десятичными (шаг 0.01) -->
<input type="number" name="price" min="0" step="0.01">
<!-- Любое число -->
<input type="number" name="value" step="any">
pattern -- регулярное выражение
<!-- Только буквы (кириллица + латиница) -->
<label for="name">Имя:</label>
<input type="text" id="name" name="name"
pattern="[A-Za-zА-Яа-яЁё\s]+"
title="Только буквы и пробелы">
<!-- Телефон -->
<input type="tel" name="phone"
pattern="\+7\s?\(?\d{3}\)?\s?\d{3}[-\s]?\d{2}[-\s]?\d{2}"
title="Формат: +7 (XXX) XXX-XX-XX">
<!-- Почтовый индекс -->
<input type="text" name="zip"
pattern="\d{6}"
title="6 цифр">
Атрибут title показывается в подсказке при ошибке валидации.
type -- встроенная валидация
<!-- Email: проверяет формат xxx@xxx -->
<input type="email" name="email" required>
<!-- URL: проверяет формат http(s)://... -->
<input type="url" name="website" required>
<!-- Number: принимает только числа -->
<input type="number" name="count" required>
CSS-псевдоклассы валидации
<style>
/* Валидное поле */
input:valid {
border-color: green;
}
/* Невалидное поле */
input:invalid {
border-color: red;
}
/* Невалидное только после взаимодействия */
input:user-invalid {
border-color: red;
background: #fff5f5;
}
/* В диапазоне / вне диапазона */
input:in-range { border-color: green; }
input:out-of-range { border-color: red; }
/* Required пустое */
input:placeholder-shown:required {
border-color: orange;
}
/* Optional */
input:optional {
border-color: #ccc;
}
</style>
<input type="email" name="email" required placeholder="Email">
<input type="number" name="age" min="18" max="99">
:user-invalid vs :invalid
:invalid применяется сразу (даже до взаимодействия), а :user-invalid -- только после того, как пользователь попытался ввести данные. Используй :user-invalid для лучшего UX.
novalidate -- отключение встроенной валидации
<!-- Отключить валидацию для всей формы -->
<form action="/api" method="POST" novalidate>
<input type="email" name="email" required>
<button type="submit">Отправить</button>
</form>
<!-- Отключить для конкретной кнопки -->
<form action="/api" method="POST">
<input type="email" name="email" required>
<button type="submit">Отправить</button>
<button type="submit" formnovalidate>Сохранить черновик</button>
</form>
novalidate полезен когда ты реализуешь кастомную валидацию через JavaScript.
Constraint Validation API
Проверка валидности
<form id="myForm" novalidate>
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<span class="error" aria-live="polite"></span>
<button type="submit">Отправить</button>
</form>
<script>
const form = document.getElementById('myForm');
const email = document.getElementById('email');
const error = email.nextElementSibling;
email.addEventListener('input', () => {
if (email.validity.valid) {
error.textContent = '';
}
});
form.addEventListener('submit', (e) => {
if (!email.validity.valid) {
e.preventDefault();
showError;
}
});
function showError() {
if (email.validity.valueMissing) {
error.textContent = 'Email обязателен';
} else if (email.validity.typeMismatch) {
error.textContent = 'Введите корректный email';
} else if (email.validity.patternMismatch) {
error.textContent = 'Формат не соответствует';
}
}
</script>
Объект validity
const input = document.querySelector('input');
input.validity.valid; // true если всё ОК
input.validity.valueMissing; // required и пусто
input.validity.typeMismatch; // тип не совпадает (email, url)
input.validity.patternMismatch;// pattern не совпал
input.validity.tooLong; // длиннее maxlength
input.validity.tooShort; // короче minlength
input.validity.rangeOverflow; // больше max
input.validity.rangeUnderflow; // меньше min
input.validity.stepMismatch; // не кратно step
input.validity.badInput; // невалидный ввод (буквы в number)
input.validity.customError; // установлена кастомная ошибка
setCustomValidity -- кастомная ошибка
<form>
<label for="pwd">Пароль:</label>
<input type="password" id="pwd" name="password" required minlength="8">
<label for="pwd2">Подтверждение:</label>
<input type="password" id="pwd2" name="password_confirm" required>
<button type="submit">Регистрация</button>
</form>
<script>
const pwd = document.getElementById('pwd');
const pwd2 = document.getElementById('pwd2');
pwd2.addEventListener('input', () => {
if (pwd.value !== pwd2.value) {
pwd2.setCustomValidity('Пароли не совпадают');
} else {
pwd2.setCustomValidity(''); // Сброс ошибки
}
});
</script>
reportValidity и checkValidity
// checkValidity -- проверяет, генерирует событие invalid
const isValid = form.checkValidity; // true/false
// reportValidity -- проверяет И показывает нативные тултипы ошибок
form.reportValidity;
// Для одного поля
input.checkValidity;
input.reportValidity;
Полный пример: форма с кастомной валидацией
<form id="registerForm" novalidate>
<div class="field">
<label for="reg-email">Email:</label>
<input type="email" id="reg-email" name="email" required
aria-describedby="email-error">
<p class="error" id="email-error" role="alert"></p>
</div>
<div class="field">
<label for="reg-pwd">Пароль:</label>
<input type="password" id="reg-pwd" name="password"
required minlength="8"
aria-describedby="pwd-hint pwd-error">
<p class="hint" id="pwd-hint">Минимум 8 символов</p>
<p class="error" id="pwd-error" role="alert"></p>
</div>
<button type="submit">Зарегистрироваться</button>
</form>
<style>
.error { color: #d32f2f; font-size: 0.875rem; min-height: 1.25rem; }
.hint { color: #666; font-size: 0.875rem; }
.field { margin-bottom: 1rem; }
input:user-invalid { border-color: #d32f2f; }
input:user-valid { border-color: #388e3c; }
</style>
<script>
const form = document.getElementById('registerForm');
form.addEventListener('submit', (e) => {
// Очистить ошибки
form.querySelectorAll('.error').forEach(el => el.textContent = '');
let isValid = true;
form.querySelectorAll('input').forEach(input => {
if (!input.validity.valid) {
isValid = false;
const errorEl = document.getElementById(input.id.replace('reg-', '') + '-error');
if (errorEl) {
errorEl.textContent = input.validationMessage || 'Ошибка';
}
}
});
if (!isValid) {
e.preventDefault();
// Фокус на первое невалидное поле
form.querySelector('input:invalid')?.focus();
}
});
</script>
Частые ошибки
| Ошибка | Почему плохо | Как правильно |
|---|---|---|
| Только клиентская валидация | Легко обойти через DevTools | Всегда валидируй на сервере |
:invalid вместо :user-invalid |
Поля подсвечены ошибкой до ввода | :user-invalid или JS-валидация |
Нет title с pattern |
Непонятное сообщение об ошибке | title объясняет формат |
Ошибки без role="alert" |
Screen reader не объявит ошибку | role="alert" или aria-live |
| Нет фокуса на ошибке | Пользователь не видит проблему | Фокус на первое невалидное поле |
setCustomValidity без сброса |
Поле остаётся невалидным навсегда | setCustomValidity('') при исправлении |
Практика
- Создай форму с
required,minlength,patternи проверь встроенные сообщения об ошибках - Стилизуй валидные/невалидные поля через
:valid/:invalid - Реализуй проверку совпадения паролей через
setCustomValidity - Создай кастомную валидацию с
novalidateи Constraint Validation API - Добавь
aria-describedbyдля связи ошибок с полями
Связанные темы
- Формы в HTML -- контейнер формы
- input типы -- типы и их встроенная валидация
- ARIA атрибуты -- доступная обратная связь об ошибках
- Семантика для screen readers -- объявление ошибок