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

Кастомная валидация форм — проверка данных, введённых пользователем, средствами JavaScript с формированием понятных сообщений об ошибках и управлением состоянием полей, в дополнение к встроенной HTML5-валидации или вместо неё.

Зачем нужно

Встроенная HTML5-валидация (required, pattern, min/max) ограничена в кастомизации внешнего вида и не поддерживает сложные правила (совпадение паролей, уникальность email через API). Кастомная валидация на JS даёт полный контроль над UX: когда показывать ошибку, как её оформить, как проверить данные.

Где используется

  • Формы регистрации и авторизации
  • Многошаговые формы (wizard)
  • Валидация в реальном времени (при вводе / на blur)
  • Комплексные правила: зависимые поля, async-проверки

Примеры

Базовая валидация поля

function validateEmail(value) {
  if (!value) return 'Email обязателен';
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return 'Неверный формат email';
  return null; // null = валидно
}

function showError(input, message) {
  const field = input.closest('.form-field');
  const errorEl = field.querySelector('.error-message');

  input.classList.toggle('input--error', Boolean(message));
  errorEl.textContent = message || '';
  errorEl.hidden = !message;
}

const emailInput = document.getElementById('email');
emailInput.addEventListener('blur', () => {
  const error = validateEmail(emailInput.value);
  showError(emailInput, error);
});

Валидация всей формы перед отправкой

const validators = {
  name: (v) => !v.trim() ? 'Имя обязательно' : null,
  email: (v) => !v.includes('@') ? 'Неверный email' : null,
  password: (v) => v.length < 8 ? 'Минимум 8 символов' : null,
  confirm: (v, form) =>
    v !== form.password.value ? 'Пароли не совпадают' : null,
};

document.getElementById('register-form').addEventListener('submit', (event) => {
  event.preventDefault();
  const form = event.target;
  let isValid = true;

  Object.entries(validators).forEach(([fieldName, validate]) => {
    const input = form[fieldName];
    const error = validate(input.value, form);
    showError(input, error);
    if (error) isValid = false;
  });

  if (isValid) submitForm(new FormData(form));
});

Валидация в реальном времени с debounce

const debounce = (fn, ms) => {
  let t;
  return (...args) => { clearTimeout(t); t = setTimeout( => fn(...args), ms); };
};

const usernameInput = document.getElementById('username');

const checkUsername = debounce(async (value) => {
  if (!value) return showError(usernameInput, 'Обязательное поле');

  showLoading(usernameInput, true);
  const res = await fetch(`/api/check-username?q=${value}`);
  const { available } = await res.json();
  showLoading(usernameInput, false);

  showError(usernameInput, available ? null : 'Имя уже занято');
}, 400);

usernameInput.addEventListener('input', (e) => checkUsername(e.target.value));

Constraint Validation API

// Встроенный API для кастомных сообщений
const input = document.getElementById('phone');

input.addEventListener('input', () => {
  const isValid = /^\+7\d{10}$/.test(input.value);
  input.setCustomValidity(isValid ? '' : 'Формат: +79991234567');
  input.reportValidity;
});

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

  • Валидация только на клиенте — всегда дублируйте валидацию на сервере; клиентская — только UX.
  • Показывать ошибку сразу при фокусе — лучше показывать ошибку при blur (потере фокуса) или отправке, а не при первом символе.
  • Не блокировать отправку при ошибках — всегда вызывайте event.preventDefault() и проверяйте все поля перед submitForm.

Связанные темы

Ресурсы