Стандартные формы на Тильде работают, но часто не хватает маски для телефона, проверки ИНН или условного показа полей. Разберём, как добавить всё это на чистом JavaScript — без сторонних библиотек или с минимальными зависимостями.
Маска для телефона: IMask.js
Начнём с самого частого запроса — маска ввода телефона. Можно реализовать на ванильном JS, но IMask.js весит 7 КБ и экономит часы отладки.
Подключаем библиотеку через блок T123 (HTML-код) перед закрывающим </body>:
<script src="https://unpkg.com/imask@7.6.0/dist/imask.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var phoneInput = document.querySelector('input[name="Phone"]');
if (!phoneInput) return;
IMask(phoneInput, {
mask: '+{7} (000) 000-00-00',
lazy: false,
placeholderChar: '_'
});
});
</script>
Маска не даст ввести буквы, автоматически подставит скобки и дефисы. Атрибут lazy: false показывает маску сразу при фокусе.
Маска на ванильном JavaScript
Если не хотите тянуть библиотеку — вот минимальная реализация:
document.addEventListener('DOMContentLoaded', function() {
var input = document.querySelector('input[name="Phone"]');
if (!input) return;
input.addEventListener('input', function(e) {
var x = e.target.value.replace(/\D/g, '');
if (x.length === 0) { e.target.value = ''; return; }
if (x[0] === '8') x = '7' + x.slice(1);
if (x[0] !== '7') x = '7' + x;
var formatted = '+7';
if (x.length > 1) formatted += ' (' + x.slice(1, 4);
if (x.length > 4) formatted += ') ' + x.slice(4, 7);
if (x.length > 7) formatted += '-' + x.slice(7, 9);
if (x.length > 9) formatted += '-' + x.slice(9, 11);
e.target.value = formatted;
});
input.addEventListener('focus', function() {
if (!input.value) input.value = '+7';
});
input.addEventListener('blur', function() {
if (input.value === '+7') input.value = '';
});
});
Кастомная валидация
Тильда проверяет только обязательность полей и формат email. Добавим свои правила — проверку длины телефона, ИНН, формата даты.
document.addEventListener('DOMContentLoaded', function() {
var form = document.querySelector('.t-form');
if (!form) return;
var validators = {
'Phone': function(value) {
var digits = value.replace(/\D/g, '');
if (digits.length !== 11) return 'Введите 11 цифр номера';
return null;
},
'INN': function(value) {
if (!/^\d{10}$|^\d{12}$/.test(value)) return 'ИНН: 10 или 12 цифр';
return null;
},
'Date': function(value) {
if (!/^\d{2}\.\d{2}\.\d{4}$/.test(value)) return 'Формат: ДД.ММ.ГГГГ';
var parts = value.split('.');
var d = new Date(parts[2], parts[1] - 1, parts[0]);
if (isNaN(d.getTime())) return 'Некорректная дата';
return null;
}
};
var submitBtn = form.querySelector('.t-submit');
if (!submitBtn) return;
submitBtn.addEventListener('click', function(e) {
var hasErrors = false;
Object.keys(validators).forEach(function(fieldName) {
var input = form.querySelector('input[name="' + fieldName + '"]');
if (!input || !input.value) return;
var error = validators[fieldName](input.value);
var existingError = input.parentNode.querySelector('.custom-error');
if (existingError) existingError.remove();
if (error) {
hasErrors = true;
var errorEl = document.createElement('div');
errorEl.className = 'custom-error';
errorEl.textContent = error;
errorEl.style.cssText = 'color:#ff4444;font-size:12px;margin-top:4px;';
input.parentNode.appendChild(errorEl);
input.style.borderColor = '#ff4444';
} else {
input.style.borderColor = '';
}
});
if (hasErrors) {
e.preventDefault();
e.stopPropagation();
}
}, true);
});
Ключевой момент — третий аргумент true в addEventListener. Это фаза захвата: наш обработчик сработает раньше обработчика Тильды, и мы успеем заблокировать отправку.
Условные поля: показ/скрытие по выбору
Классическая задача: пользователь выбирает «Юридическое лицо» — появляются поля ИНН, КПП, название компании. Выбирает «Физическое лицо» — поля скрыты.
document.addEventListener('DOMContentLoaded', function() {
// Находим select или radio по имени поля
var typeSelect = document.querySelector('select[name="ClientType"]');
// Группы полей, которые нужно показывать/скрывать
var companyFields = document.querySelectorAll(
'[data-input-lid="1234567890"], [data-input-lid="1234567891"]'
);
function toggleFields() {
var isCompany = typeSelect.value === 'Юридическое лицо';
companyFields.forEach(function(field) {
var wrapper = field.closest('.t-input-group');
if (!wrapper) return;
wrapper.style.display = isCompany ? '' : 'none';
// Снимаем обязательность для скрытых полей
var input = wrapper.querySelector('input');
if (input) {
if (isCompany) {
input.setAttribute('required', '');
} else {
input.removeAttribute('required');
input.value = '';
}
}
});
}
if (typeSelect) {
typeSelect.addEventListener('change', toggleFields);
toggleFields(); // начальное состояние
}
});
Чтобы найти data-input-lid нужного поля — откройте страницу, кликните правой кнопкой по полю → «Исследовать элемент». В атрибутах контейнера увидите ID.
Условные поля через радиокнопки
Если вместо select используются радиокнопки:
document.addEventListener('DOMContentLoaded', function() {
var radioButtons = document.querySelectorAll('input[name="DeliveryType"]');
var addressBlock = document.querySelector('.t-input-group_address');
var pickupBlock = document.querySelector('.t-input-group_pickup');
function handleChange() {
var selected = document.querySelector('input[name="DeliveryType"]:checked');
if (!selected) return;
if (selected.value === 'Доставка') {
addressBlock.style.display = '';
pickupBlock.style.display = 'none';
} else {
addressBlock.style.display = 'none';
pickupBlock.style.display = '';
}
}
radioButtons.forEach(function(radio) {
radio.addEventListener('change', handleChange);
});
handleChange();
});
Многошаговая форма (wizard)
Разбиваем длинную форму на шаги. Каждый шаг — это блок, который показывается или скрывается. Между шагами — кнопки «Назад» и «Далее».
document.addEventListener('DOMContentLoaded', function() {
var form = document.querySelector('.t-form');
if (!form) return;
var steps = form.querySelectorAll('.js-form-step');
var currentStep = 0;
// Создаём навигацию
var nav = document.createElement('div');
nav.style.cssText = 'display:flex;gap:12px;margin-top:20px;justify-content:space-between;';
nav.innerHTML =
'<button type="button" class="js-step-prev" style="padding:10px 24px;background:#e0e0e0;border:none;border-radius:6px;cursor:pointer;font-size:14px;">Назад</button>' +
'<span class="js-step-counter" style="line-height:40px;font-size:14px;color:#888;"></span>' +
'<button type="button" class="js-step-next" style="padding:10px 24px;background:#222;color:#fff;border:none;border-radius:6px;cursor:pointer;font-size:14px;">Далее</button>';
form.appendChild(nav);
var prevBtn = nav.querySelector('.js-step-prev');
var nextBtn = nav.querySelector('.js-step-next');
var counter = nav.querySelector('.js-step-counter');
var submitBtn = form.querySelector('.t-submit');
function showStep(index) {
steps.forEach(function(step, i) {
step.style.display = i === index ? '' : 'none';
});
prevBtn.style.visibility = index === 0 ? 'hidden' : 'visible';
nextBtn.style.display = index === steps.length - 1 ? 'none' : '';
submitBtn.style.display = index === steps.length - 1 ? '' : 'none';
counter.textContent = 'Шаг ' + (index + 1) + ' из ' + steps.length;
currentStep = index;
}
function validateStep(index) {
var stepEl = steps[index];
var inputs = stepEl.querySelectorAll('input[required], select[required], textarea[required]');
var valid = true;
inputs.forEach(function(inp) {
if (!inp.value.trim()) {
inp.style.borderColor = '#ff4444';
valid = false;
} else {
inp.style.borderColor = '';
}
});
return valid;
}
nextBtn.addEventListener('click', function() {
if (validateStep(currentStep) && currentStep < steps.length - 1) {
showStep(currentStep + 1);
}
});
prevBtn.addEventListener('click', function() {
if (currentStep > 0) showStep(currentStep - 1);
});
showStep(0);
});
Чтобы это работало, оберните группы полей в Zero-блоки или используйте T123 для создания обёрток с классом js-form-step.
Стилизация ошибок валидации
Добавьте CSS через блок T123 или «Настройки сайта → Head»:
.custom-error {
color: #e74c3c;
font-size: 12px;
margin-top: 4px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.t-input-group input:focus {
border-color: #222 !important;
transition: border-color 0.2s;
}
/* Прогресс-бар для многошаговой формы */
.step-progress {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.step-progress__item {
flex: 1;
height: 4px;
background: #e0e0e0;
border-radius: 2px;
transition: background 0.3s;
}
.step-progress__item.active {
background: #222;
}
Маска даты без библиотек
function dateMask(input) {
input.addEventListener('input', function(e) {
var v = e.target.value.replace(/\D/g, '');
if (v.length > 8) v = v.slice(0, 8);
var result = '';
if (v.length > 0) result = v.slice(0, 2);
if (v.length > 2) result += '.' + v.slice(2, 4);
if (v.length > 4) result += '.' + v.slice(4, 8);
e.target.value = result;
});
input.setAttribute('placeholder', 'ДД.ММ.ГГГГ');
input.setAttribute('maxlength', '10');
}
var dateInput = document.querySelector('input[name="Date"]');
if (dateInput) dateMask(dateInput);
Итого: чек-лист
| Задача | Решение | Библиотека |
|---|---|---|
| Маска телефона | IMask.js или ванильный JS | IMask (7 КБ) / нет |
| Маска даты | Ванильный JS | Нет |
| Валидация ИНН, email, телефона | Кастомные правила на submit | Нет |
| Условные поля | show/hide по change | Нет |
| Многошаговая форма | JS wizard с валидацией шагов | Нет |
Все скрипты добавляются через блок T123 (Вставка кода) или через раздел «Head / Footer» в настройках страницы. Не забудьте обернуть код в DOMContentLoaded — иначе скрипт может выполниться до рендера формы.
Есть идея? Реализуем
Разрабатываем проекты, которые решают задачи бизнеса — от лендинга до сложного сервиса. Расскажите о своей задаче, подберём решение.

