Встроенная валидация и UX ошибок
1) Принципы
1. Предиктивная помощь, не наказание. Показываем, как ввести корректно, до ошибки.
2. Не терять данные. Любая ошибка не уничтожает введенное; поддерживаем «Отменить»/«Повторить».
3. Момент показа.
До ввода: helper text (правила и примеры).
Во время: мягкие хинты/маска/автоподстановка.
После (blur/submit): четкая ошибка с инструкцией «как исправить».
4. Экономия внимания. Одно сообщение — одна причина и действие.
5. Доступность. Текст + иконка/цвет, связь с полем через ARIA, фокус к первому ошибочному полю.
2) Слои валидации
Клиентская синхронная: формат, длина, обязательность, маска. Быстрая и дешевая.
Клиентская асинхронная: уникальность логина, проверка BIN/IBAN, API-подсказки. С дебаунсом.
Серверная: бизнес-правила, лимиты, риск-скоринг, авторизация/права. Истина последней инстанции.
Правило: даже при идеальной клиентской проверке сервер подтверждает и формирует окончательный текст.
3) Тайминги и дебаунс
Валидация на blur → мгновенный фидбек ≤ 100 мс.
Асинхронная проверка — дебаунс 250–400 мс после остановки ввода.
Подтверждение успешности — лаконичное («Ок») или зеленая иконка; без «салютов».
На `submit` показываем список ошибок и переносим фокус к первой.
js const debounce = (fn, ms=300)=>{let t;return (...a)=>{clearTimeout(t);t=setTimeout(()=>fn(...a),ms)}}
4) Копирайтинг ошибок
Шаблон: причина → как исправить → альтернатива (если есть).
Хорошо: «Пароль короче 8 символов. Добавьте еще символы или используйте фразу».
Хорошо: «IBAN выглядит некорректно. Проверьте длину и символы: A–Z, 0–9».
Плохо: «Неверный ввод».
Не вините пользователя; избегайте жаргона и кодов.
В чувствительных зонах (платежи/KYC) избегаем деталей, раскрывающих безопасность.
5) Паттерны показа сообщений
5.1 У поля (inline)
`aria-invalid="true"`, текст в `aria-describedby`.
Коротко, конкретно; без длинных абзацев.
Цвет + иконка, но смысл — в тексте.
5.2 Под формой (summary)
Список всех ошибок с якорями к полям.
Кнопка «Перейти к ошибке»/клик по пункту переносит фокус.
5.3 В процессе отправки
Блокируем повторное нажатие (state `busy`).
При таймауте 3–5 с — «Ожидаем подтверждение…» с безопасным повтором.
6) Маски, автоподсказки и корректоры
Маски не должны мешать вставке/редактированию. Разрешайте свободный ввод, нормализуйте под капотом.
Автоподсказки: показывайте примеры форматов, placeholder как подсказка, а не «обязательная» часть.
Нормализация: trimming пробелов, унификация регистров, авто-формат (например, `+1 (___) ___-____`) — но в исходные данные сохраняйте «чистую» версию.
7) Доступность (A11y)
Связь: `label` ↔ `input`, ошибки в `aria-describedby`.
Критичные — `role="alert"`, информационные — `role="status"`.
Возвращаем фокус к ошибочному полю, `:focus-visible` видим.
Контраст текста/иконок ≥ AA; смысл не зависит только от цвета.
html
<label for="email">Email</label>
<input id="email" name="email" aria-describedby="email-help email-err">
<small id="email-help">Например: user@domain.tld</small>
<p id="email-err" class="field-error" role="alert" hidden>Проверьте формат email</p>
8) Международные форматы и локализация
Имена/адреса: допускайте разные алфавиты, длины, апострофы, дефисы.
Даты/валюты/номера: используйте локальные форматы ввода и строгие внутренние структуры хранения (ISO/число центов).
Телефон: ввод в международном формате `+CC`, автоподсказка по стране.
Язык сообщений: короткие, культурно нейтральные; закладывайте +20–30% длины строки.
9) Безопасность и приватность
Не показывайте, что аккаунт существует/не существует — общий текст: «Если email зарегистрирован, мы отправим письмо».
Маскируйте чувствительные данные (PAN, паспорт).
На критичных шагах (ставка/платеж) используйте идемпотентность и «безопасные повторы».
Логи — с ID корреляции без PII в сообщениях.
10) Сохранение прогресса
Автосейв черновика (локально/на сервере).
При ошибке отправки — форма остается заполненной; предлагается повтор позже.
При многошаговых процессах (KYC) — сохраняйте завершенные шаги.
11) Асинхронная валидация
Дебаунс 250–400 мс; показываем «проверяем…» возле поля, не блокируя весь экран.
Ясная индикация успеха/неуспеха без «дерганий» макета.
Таймаут сети → «Не удалось проверить. Продолжить с риском?» (если допустимо) или «Повторить».
js const checkUsername = debounce(async (v)=>{
state.usernameChecking = true;
const ok = await api.unique('username', v).catch(()=>null);
state.usernameChecking = false;
state.usernameValid = ok === true;
}, 300);
12) Платежные формы и KYC (специфика)
Карты: формат PAN, срок, CVC — валидация по мере ввода; ошибки — без раскрытия причины отклонения банком.
A2A/кошельки: проверка допустимости по стране/лимитам, четкие тексты про комиссии/сроки.
KYC: пошаговые требования к фото/документам, предпросмотр, размер/тип файла, сроки проверки, приватность.
Ответственная игра: сообщения нейтральные, с действиями «Установить лимит»/«Помощь».
13) Антипаттерны
Показ ошибок «на каждый символ» без дебаунса.
Сброс формы при ошибке.
Сообщение «Неверный ввод» без указания поля/правила.
Критичная информация только цветом/иконкой.
Блокировка всей страницы ради проверки одного поля.
Отсутствие оффлайн-режима и повторов при сетевых сбоях.
14) Токены дизайн-системы (пример)
json
{
"validation": {
"debounceMs": 300,
"blurFeedbackMs": 100,
"asyncTimeoutMs": 5000,
"summaryMaxItems": 6
},
"a11y": {
"useAriaDescribedby": true,
"errorRole": "alert",
"statusRole": "status"
},
"visual": {
"fieldGap": 8,
"iconSize": 16,
"borderRadius": 10
}
}
15) Сниппеты реализации
Поле с inline-валидатором (формат + серверная проверка):js const rules = {
email: v => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) "Проверьте формат email"
};
async function validateEmail(v){
const fmt = rules.email(v);
if (fmt!== true) return fmt;
try {
const r = await fetch(`/api/email/check?v=${encodeURIComponent(v)}`);
const { allowed } = await r.json();
return allowed? true: "Этот email недоступен. Выберите другой.";
} catch {
return "Не удалось проверить. Повторите позже.";
}
}
Сводка ошибок с фокусом:
ts function focusFirstError() {
const err = document.querySelector('[aria-invalid="true"]');
if (err) err.focus({ preventScroll:false });
}
Сохранение черновика локально:
js const saveDraft = debounce(form => localStorage.setItem('draft', JSON.stringify(Object.fromEntries(new FormData(form)))), 500);
form.addEventListener('input', ()=>saveDraft(form));
16) Метрики и контроль
Time-to-Fix (время от ошибки до исправления).
Error rate по полям и по причинам (формат/лимиты/сервер).
Повторная отправка (retry success rate).
Доля брошенных форм и глубина шага.
Заполненность подсказками: CTR «Подробнее», частота скрытий подсказок.
17) QA-чек-лист
A11y
- Фокус переходит к первому ошибочному полю; `aria-describedby`/`aria-invalid` выставлены.
- Контраст ≥ AA; сообщения не зависят только от цвета.
Поведение
- Встроенная валидация с дебаунсом; ошибки появляются не раньше blur (кроме критичных масок).
- На submit формируется сводка, поля не очищаются.
- Асинхронные проверки не блокируют страницу; есть таймаут и повтор.
Текст
- Причина + как исправить; без кодов/вины.
- Локализация не ломает макет; примеры актуальны.
Безопасность
- Нет утечек PII в сообщениях; не раскрываем существование аккаунта.
- Идемпотентность для критичных операций.
18) Документация в дизайн-системе
Компоненты: `FieldError`, `FormSummary`, `HelperText`, `BusyButton`.
Карты правил для типовых полей (email, телефон, пароль, адрес, IBAN, дата).
Гайды по дебаунсу, асинхронной проверке и оффлайн-поведению.
Шаблоны текстов для частых ошибок и примеры «до/после».
Краткое резюме
Встроенная валидация — это про предиктивную помощь, четкие инструкции и бережное отношение к данным. Проверяйте локально и на сервере, показывайте ошибки в нужный момент с конкретными действиями, уважайте доступность и приватность, сохраняйте прогресс и используйте дебаунс. Так формы становятся дружелюбными, а ошибки — быстрыми и исправимыми.