Маски вводу та UX-форми
1) Принципи
1. Допомагати, не карати. Маска направляє введення і знижує помилки, але не блокує друк і вставку.
2. Дані ≠ відображення. Зберігаємо «сирі» нормалізовані значення, форматуємо тільки в UI.
3. Передбачуваний курсор. Будь-яка автопідстановка не «стрибає» caret і не ламає undo/redo.
4. Локаль і пристрій. Клавіатури, роздільники, календар і валюти - по регіону і платформі.
5. Доступність і приватність. Текст + іконка/колір; чутливі поля маскуємо, але не заважаємо менеджерам паролів/автозаповнення.
2) Коли маска доречна (і коли ні)
Використовувати:- Формати зі стійкою структурою: телефон, IBAN, PAN (карти), CVC, дата, час, індекс, OTP.
- Грошові суми з роздільниками (при друку - «чисте» введення, при блюрі - формат).
- Коди (реф. коди, промо), фіксовані довжини.
- Імена/адреси/Email (маска обмежує допустимі символи/мови).
- Складні вільні поля (коментарі, назви компаній).
- Введення з потенційно невідомим форматом (міжнародні номери без країни).
3) Маска vs автоформат vs валідація
Маска - підказка структури «на льоту» (дужки, дефіси); не повинна ламати введення/вставку.
Автоформат - застосовуємо при блюрі/втраті фокусу (тисячі, прогалини IBAN).
Валідація - логіка коректності (довжина, контрольні суми), показ помилок після «blur» або «submit».
Правило: маска не замінює валідацію, а автоформат не повинен змінювати сенс введеного.
4) Клавіатури та атрибути HTML
Підберіть коректні типи/режими, щоб прискорити введення і знизити помилки:5) Карет, копіпаст і нормалізація
Не ламати caret: при авто-вставці символів (пробіли/дужки) коригуйте позицію курсора.
Копіпаст: при вставці очищайте від пробілів/дефісів → валідуйте → відображайте з форматуванням.
Нормалізація: триммінг, заміна «кривих» символів («O'→'0» не можна!), переведення у верхній регістр для IBAN, єдиний формат дати в сховищі (ISO).
js const clean = s => s. replace(/[^\da-zA-Z]/g,'');
const normalizePAN = s => clean(s). slice (0.19) ;//no spaces/hyphens const normalizeIBAN = s => clean (s). toUpperCase(); // A–Z0–9
6) Числа, валюти і локалі
Введення «як друкується» (допуск','або'.'як роздільника), зберігання в minor units (копійки/центи).
Відображення по локалі (угруповання тисяч) на блюрі/після сабміту; у фокусі показуйте «сире» значення для зручності редагування.
Явно вказуйте валюту і фіксуйте точність (наприклад, 2 знаки).
js function parseMoney(input) {
//resolve both comma and period as decimal const s = input. replace(/\s/g,''). replace(',', '.');
const num = Number(s);
if (Number. isNaN(num)) return null;
return Math. round(num 100); // cents
}
function formatMoney(cents, locale='ru-RU', currency='RUB') {
return (cents/100). toLocaleString(locale, { style:'currency', currency });
}
7) Дати і час
Якщо нативні пікери незручні/різні на платформах - використовуйте текстове поле з маскою'DD. MM. YYYY', але зберігайте ISO'YYYY-MM-DD'.
Перевірка реальності дат (29. 02, діапазони), таймзони - на сервері.
Додайте кнопки «Сьогодні», «Зараз», «Очистити».
8) Телефони та країни
Два поля: країна (+ код) і номер або «розумна» маска по обраній країні.
При вставці повного'+ CC...'автозаповніть країну.
Зберігайте E.164 ('+ CCXXXXXXXXX'), показуйте локально з пробілами.
9) Платіжні реквізити: PAN/IBAN/CVC/EXP
PAN: групування 4-4-4-4/4; у значенні - тільки цифри; Luhn-check; ніяких логів/аналітики з PAN.
CVC: ' password'-стиль (приховано),'autocomplete =» cc-csc»', не зберігати в чернетки.
EXP: 'MM/YY', авто-вставка '/' після 2 цифр, перевірка діапазону 01-12 і розумного року.
IBAN: upper-case, пробіли тільки в UI; перевірка довжини по країні і контрольної суми.
10) ОТР/код підтвердження
6 (або N) осередків з автофокусом і авто-переходом, вставка з буфера розпізнає весь код.
' autocomplete =» one-time-code»', на мобільних - автовидобування з СМС.
Бекап-введення без спліт-полів (одне поле) - для скрінрідерів.
html
<div class="otp" role="group" aria-label="Код из SMS">
<input inputmode="numeric" maxlength="1">
<input inputmode="numeric" maxlength="1">
<!-- … -->
</div>
11) Маски і a11y
Лейбл обов'язковий ('< label for>'), placeholder - приклад, а не заміна.
Пояснюйте правило поруч: helper text з прикладом ("Формат: + CC ХХХ ХХХ-ХХ-ХХ").
Помилки зв'язуйте через'aria-describedby', критичні -'role = «alert»'.
Контраст тексту і контурів ≥ AA,':focus-visible'не приховувати.
12) Приватність і безпека
Чутливі поля: не логувати, не писати в RUM, не зберігати в чернетки (PAN, CVC, паспорт).
Маски та форматування не повинні розкривати валідність облікового запису («Якщо email зареєстрований...» - нейтральне формулювання).
Ідемпотентність і retry для критичних сабмітів (платіж/ставка).
13) Поведінка форм і продуктивність
Дебаунс асинхронних перевірок (250-400 мс), видима індикація «Перевіряємо»....
Не блокуйте весь екран заради одного поля; локальний спінер/скелет.
Батчіть зміни DOM; анімуйте тільки'transform/opacity'.
На мобільних - уникайте «стрибків» при появі клавіатури (safe-area, viewport meta).
14) Код-сніпети
М'яка маска телефону (без ломки вставки):js function formatPhoneVisible(value) {
const d = value. replace(/\D/g,''). slice(0,15);
if (!d) return '';
if (d. startsWith('7') d. startsWith('8')) {
return d. replace(/^([78])? (\d{3})(\d{3})(\d{2})(\d{2})./, '+7 ($2) $3-$4-$5');
}
// generic E.164 grouping: +CC XXX XXX XX XX return d. replace(/^(\d{1,3})(\d{0,3})(\d{0,3})(\d{0,2})(\d{0,2})./, (m,c1,c2,c3,c4,c5)=>
`+${c1}${c2?` ${c2}`:''}${c3?` ${c3}`:''}${c4?` ${c4}`:''}${c5?` ${c5}`:''}`.trim());
}
const input = document. querySelector('#phone');
input. addEventListener('input', e => {
const raw = e. target. value;
const pos = e. target. selectionStart;
const digitsBefore = raw. slice(0,pos). replace(/\D/g,''). length;
const cleaned = raw. replace(/[^\d+]/g,'');
const visible = formatPhoneVisible(cleaned);
e. target. value = visible;
// restore caret by counting digits let p = 0, count = 0;
while (p < e. target. value. length && count < digitsBefore) { if (/\d/.test(e. target. value[p])) count++; p++; }
e. target. setSelectionRange(p, p);
});
Сума: «сире у фокусі → формат при блюрі»:
js const amount = document. getElementById('amount');
let cents = null;
amount. addEventListener('focus', () => {
if (cents!=null) amount. value = String(cents/100). replace('.', ',');
});
amount. addEventListener('blur', () => {
const v = parseMoney(amount. value) ;//from section 6 if (v = = null) return; cents = v;
amount. value = formatMoney(cents, 'ru-RU', 'RUB');
});
IBAN: upper-case і групування при блюрі:
js const iban = document. getElementById('iban');
iban. addEventListener('input', () => iban. value = iban. value. toUpperCase());
iban. addEventListener('blur', () => {
const raw = normalizeIBAN(iban. value);
iban. dataset. raw = raw ;//for iban submission. value = raw. replace(/(.{4})/g,'$1 '). trim () ;//view only
});
15) Токени дизайн-системи (приклад)
json
{
"input": {
"radius": 10,
"height": { "sm": 36, "md": 40, "lg": 48 },
"gap": 8,
"icon": 16
},
"mask": {
"debounceMs": 300,
"otpLength": 6,
"moneyPrecision": 2,
"phoneMaxDigits": 15
},
"a11y": {
"focusRing": { "width": 2, "offset": 2 },
"contrastAA": true
}
}
CSS-пресети:
css
.input { height:40px; padding:0 12px; border-radius:10px; }
.input:focus-visible { outline:2px solid var(--focus-ring); outline-offset:2px; }
.field-error { color: var(--role-danger); font-size:.875rem; margin-top:6px; }
.otp input { width:40px; text-align:center; }
16) Специфіка iGaming
Платежі/висновки: PAN/IBAN/amount з м'якими масками; сувора ідемпотентність і відсутність логів чутливих полів; підказки про комісії і терміни.
KYC: маски для дат, паспортних номерів (без «жорсткої» фільтрації - облік різних форматів), розмір/тип файлу, перегляд.
Ліміти та відповідальна гра: зрозумілі суми/періоди (дні/тижні/місяці), хелпери поруч, контраст AAA.
Ставки: швидке введення суми (кнопки-пресети + поле), у фокусі «сире» число, при блюрі формат по локалі; недопуск «, «...../подвійного роздільника.
17) Анти-патерни
Жорсткі маски, що забороняють допустимі символи/вставку.
Стрибаючий caret при автоформаті; пропажа виділення/undo.
Placeholder замість лейблу.
Авто-додавання валюти всередину значення (ламає копіпаст).
Помилки «на кожен символ» без дебаунсу.
Локалезалежні формати в сховищі (зберігайте ISO/числа).
Логування PAN/паспортних номерів і показ «занадто чесних» причин відхилення.
18) Метрики та експерименти
Error rate по полях (до/після маски).
Time-to-Complete форми і повторні відправки.
Частка невдалих вставок (копіпаст) і «відкатів» (undo).
CTR підказок/прикладів, частка автозаповнень.
Abandon rate на кроці платежу/КУС.
19) QA-чек-лист
Введення та caret
- Вставка з буфера не ламається, пробіли/дефіси очищаються коректно.
- Caret залишається передбачуваним після автоформату.
Локаль і формат
- Суми допускають', '/'.'; зберігання в minor units.
- Дати парсяться і валідуються; зберігання в ISO.
A11y
- Лейбли і'aria-describedby'підключені;'role = «alert»'для критичних.
- Контраст і фокус-кільця відповідають AA.
Безпека
- Чутливі поля не логуються/не кешуються.
- Ідемпотентність і retry на критичних кроках.
UX
- Placeholder - приклад, не лейбл; helper поруч.
- Маски не перешкоджають друку на мобільних; коректні клавіатури («inputmode»).
20) Документація в дизайн-системі
Компоненти: `MaskedInput`, `MoneyInput`, `PhoneInput`, `OtpInput`, `IbanInput`.
Токени масок (довжини/шаблони), правила caret/вставки, локалізація чисел/дат.
Гайди по приватності (чого не логувати), по доступності і по автоформату vs блюр.
«Do/Don't» з реальними прикладами і метриками до/після.
Коротке резюме
Маски і форми хороші, коли вони прискорюють введення, зберігають дані чистими і не заважають. Форматуйте дбайливо, нормалізуйте на вході, зберігайте в стабільних видах, враховуйте локалі і доступність. Тоді форми стають швидкими і зрозумілими - особливо в чутливих сценаріях платежів, KYC і ставок.