Чекбокс согласия на обработку данных в форме Тильды
Галочку «согласен с политикой» в Тильде ставят в редакторе за минуту, и на вид всё работает: без неё форма не отправляется. Но проверка обязательности считает отмеченные поля по имени, а имя у чекбокса по умолчанию одно и то же — Checkbox. Два таких чекбокса в одной форме, и любой из них закрывает требование обоих. В заявке при этом не остаётся ни текста согласия, ни адреса политики. Всё проверено вызовами её собственного валидатора.
Короткий ответ
Обязательность чекбокса Тильда проверяет по имени поля: [name="…"]:checked во
всей форме. Имя по умолчанию у всех чекбоксов одинаковое, поэтому второй чекбокс
закрывает требование первого — разведите имена в редакторе. В заявку уезжает
только Checkbox: yes: ни текста согласия, ни адреса политики там нет.
Что стоит в разметке
Это дословно из настоящей страницы Тильды, снятой целиком:
<div class="t-input-group t-input-group_cb " data-input-lid="3523145038416"
data-field-type="cb" data-field-name="Checkbox">
<div class="t-input-block ">
<label class="t-checkbox__control t-checkbox__control_flex t-text t-text_xs">
<input type="checkbox" name="Checkbox" value="yes"
class="t-checkbox js-tilda-rule" data-tilda-req="1" aria-required="true">
<div class="t-checkbox__indicator"></div>
<span>Я согласен с <a href="…/policy" target="_blank"
rel="noreferrer noopener">политикой по работе с персональными данными</a></span>
</label>
</div>
<div class="t-input-error" aria-live="polite" id="error_3523145038416"></div>
</div>
Сделано аккуратно: настоящий <input type="checkbox">, подпись обёрнута в
<label>, у ссылки rel="noreferrer noopener", контейнер ошибки с
aria-live="polite". Три вещи из этой разметки решают всё дальнейшее:
name="Checkbox", value="yes" и data-tilda-req="1".
Обязательность считается по имени
Вот как проверка решает, отмечен ли обязательный чекбокс:
var d = !!parseInt(u.getAttribute('data-tilda-req') || 0, 10);
var A = 'checkbox' === y || 'radio' === y;
var F = r.querySelectorAll('[name="' + b + '"]:checked').length;
…
if (d && (E || A && !F)) v.type.push('req');
b — это name поля, r — вся форма. То есть требование считается закрытым, если
в форме отмечен любой элемент с этим именем.
Замер: два чекбокса в одной форме, оба с разметкой выше, оба name="Checkbox" —
первый «согласен с политикой», второй «хочу получать новости». Прогнал её
собственный валидатор, window.tildaForm.validate(form):
| Что отмечено | Ошибок | Каких |
|---|---|---|
| ничего | 2 | Checkbox:req, Checkbox:req |
| согласие | 0 | — |
| только новости | 0 | — |
Форма отправляется, согласия нет. Ни ошибки в консоли, ни следа в заявке — по которой это уже не восстановить, см. следующий раздел.
Лечится настройками, кода не нужно: в редакторе у каждого чекбокса задайте своё
имя переменной — Consent и News. После этого имена в проверке разные, и
каждое требование отвечает за себя.
Что на самом деле уезжает в заявку
Замер тем же сериализатором, которым Тильда собирает заявку. Отмечены оба чекбокса, в текстовом поле имя:
Name = Иван
Checkbox = yes
Checkbox = yes
| Что искали в заявке | Есть |
|---|---|
| текст рядом с галочкой («согласен с политикой…») | нет |
| адрес политики | нет |
| различие между согласием и новостями | нет — две одинаковые строки |
| время отметки | нет |
Причина простая: сериализатор берёт name и value полей и больше ничего.
Подписи, ссылки и текст политики живут в разметке, а не в значениях.
if (!(!r[a].name || r[a].disabled || ['file','reset','submit','button'].indexOf(r[a].type) > -1 || n(r[a])))
if ('select-multiple' !== r[a].type) {
if (!(['checkbox','radio'].indexOf(r[a].type) > -1) || r[a].checked) { … e.push({name: i, value: s}) }
}
Отсюда же второе: неотмеченный чекбокс в заявке не оставляет ничего. Не
Checkbox: no, а вообще ничего — условие || r[a].checked отсекает его до
push. Если по заявке нужно видеть «на новости не подписался», для этого нужно
отдельное поле, а не отсутствие поля.
Что положить в заявку, чтобы через год было понятно, на что человек согласился: адрес политики и её версию. Одно скрытое поле, значение прописано в разметке — кода не нужно вовсе:
<input type="hidden" name="PolicyVersion" value="policy-2026-07-28">
Сериализатору .t-input-group не нужен — достаточно name и отсутствия
disabled; это проверено отдельным замером на скрытом поле без группы. Менять
версию в этом поле нужно ровно тогда, когда меняется политика: значение в заявке
и есть ответ на вопрос, какую редакцию человек видел.
Где про это читать
Юридическую достаточность такой записи оценивает юрист, а не эта статья. Здесь только техника: в заявке есть то, что вы туда положили, и ничего кроме. Но отправить читателя к юристу и на этом закончить — половина ответа, поэтому вот первоисточники, по которым видно, зачем запись в заявке нужна вообще.
Первое: собирая данные через форму на своём сайте, оператором персональных данных становитесь вы, а не Тильда — она обрабатывает их по вашему поручению. Так это описано в её собственной справке, «Обработка персональных данных на сайте»; оттуда же рекомендация про чекбокс со ссылкой на политику под каждой формой и про саму опубликованную политику.
Второе: требования к согласию — в статье 9 152-ФЗ. Часть 1 говорит, каким согласие должно быть — «конкретным, предметным, информированным, сознательным и однозначным» — и что дано оно может быть в любой форме, позволяющей подтвердить факт его получения. Часть 3 говорит, кто подтверждает: обязанность предоставить доказательство получения согласия возлагается на оператора. Рядом стоит часть 1 статьи 15 того же закона — про обработку для продвижения товаров и услуг: она считается идущей без предварительного согласия, пока оператор не докажет обратное.
Техническая часть статьи упирается ровно сюда. Доказывать — вам, а в заявке лежит
Checkbox = yes без подписи, без адреса политики и без времени; две галочки с
одним именем дают две одинаковые строки, по которым согласие на обработку и
подписку на новости не различить. Отсюда и оба совета выше — развести имена полей
и положить версию политики скрытым полем: не «чтобы юрист был доволен», а чтобы у
записи было содержание.
Смотреть стоит действующий текст по ссылке, а не пересказ, включая этот: часть 1 статьи 9 правилась 266-ФЗ от 14.07.2022 и 156-ФЗ от 24.06.2025.
Клик по ссылке внутри подписи
Обычное опасение: ссылка на политику лежит внутри <label>, значит клик по ней
переключит галочку. Замер на её же разметке:
| Куда кликнули | Галочка |
|---|---|
| по ссылке на политику | не переключилась |
| по тексту рядом со ссылкой | переключилась |
Так и должно быть: браузер не передаёт активацию <label> дальше, если клик
пришёл по интерактивному элементу внутри неё. Плюс target="_blank" — политика
открывается в новой вкладке и не уносит заполненную форму. Здесь у Тильды всё в
порядке, править нечего.
Сообщение об ошибке видно, но не полю
Как выглядит ошибка на незакрытом согласии — замер после showErrors:
| Что | Значение |
|---|---|
текст в .t-input-error | Обязательное поле |
| класс группы | … t-input-group_cb js-error-control-box |
aria-invalid на input | не поставлен |
aria-describedby на input | не поставлен |
id у самого input | нет |
| поле в дереве доступности | checkbox «Я согласен с политикой…», invalid=false |
Текст ошибки объявится: .t-input-error живёт с aria-live="polite". Но само
поле остаётся в дереве доступности корректным, и связи «у этого чекбокса вот эта
ошибка» нет ни одной. Кто вернулся к форме через минуту, ошибку услышит уже не
здесь и не сейчас.
Это правится своим кодом на tildaform:aftererror — событие всплывает, поэтому
слушать удобно на документе:
document.addEventListener('tildaform:aftererror', function (e) {
e.target.querySelectorAll('.js-error-control-box').forEach(function (group) {
var input = group.querySelector('input, textarea, select');
var error = group.querySelector('.t-input-error');
if (!input || !error) return;
if (!error.id) error.id = 'err-' + Math.random().toString(36).slice(2);
input.setAttribute('aria-invalid', 'true');
input.setAttribute('aria-describedby', error.id);
});
});
Класс js-error-control-box ставит сама Тильда — это её собственный признак
группы с ошибкой, и опираться на него надёжнее, чем искать непустые контейнеры.
Снимать aria-invalid нужно на change поля, иначе он останется висеть после
исправления.
Своё сообщение вместо «Обязательное поле» тоже ставится без кода: рантайм сначала
ищет пользовательский текст (t_forms__getCustomMessage) и только потом берёт
свой — в редакторе это поле «Текст ошибки».
Что проверить у себя
// 1. Имена всех чекбоксов формы — совпадений быть не должно
[...document.querySelectorAll('.t-form input[type="checkbox"]')]
.map(c => c.name + (c.dataset.tildaReq ? ' (обязательный)' : ''));
// 2. Сколько полей закрывают требование каждого имени
new Set([...document.querySelectorAll('.t-form input[type="checkbox"]')].map(c => c.name));
// 3. Что уедет в заявку прямо сейчас
t_serializeArray(document.querySelector('.t-form')).map(f => f.name + '=' + f.value);
// 4. Есть ли в заявке след политики
t_serializeArray(document.querySelector('.t-form')).some(f => /policy|Policy/.test(f.name + f.value));
// 5. Ссылка в подписи открывается в новой вкладке
[...document.querySelectorAll('.t-checkbox__control a')].map(a => a.target + ' ' + a.rel);
Если во второй проверке множество короче списка из первой — имена совпадают, и обязательность у вас работает не так, как выглядит.
Свободные моды библиотеки — в каталоге. Если в форме нужны согласия по нескольким основаниям, запись версии политики со временем или связь ошибок с полями по всему сайту сразу — это доработка форм.