Перейти к содержанию

Квиз для сайта: что это, когда поднимает конверсию и код формы

Квиз для сайта: что такое квиз-форма, когда она помогает лендингу, цены Marquiz и Тильды и готовый код квиза с отправкой ответов в api.php и Keitaro…

Квиз для сайта: что это, когда поднимает конверсию и код формы

Квиз для сайта это форма заявки, разбитая на шаги. Сначала посетитель отвечает на несколько вопросов с готовыми вариантами, а на последнем шаге оставляет имя и телефон. В справке Тильды это сформулировано так: маркетинговый квиз «это пошаговая форма, которая поможет вовлечь клиента в выбор услуги или товара, уточнить его желания и собрать эти данные, чтобы упростить процесс продаж».

Ниже разбор: чем квиз отличается от обычной формы, когда он помогает лендингу, а когда мешает, где его собрать без программиста и готовый код квиза на 2026 год. Код работает с клавиатуры и на телефоне, без JavaScript превращается в обычную форму и отправляет ответы вместе с лидом в api.php и в Keitaro. Всё проверено в Chromium.

Что такое квиз-форма и чем она отличается от обычной

ПараметрОбычная формаКвиз-форма
Экранвсе поля сразуодин вопрос на экран
Ответытекстовые поляварианты на выбор, текст только в контактах
Прогрессне нуженсчётчик «Шаг 2 из 4» и полоса
Что получает операторимя и телефонимя, телефон и ответы на вопросы
Таблица прокручивается вбок

В интерфейсах такой приём называют мастером (wizard). Nielsen Norman Group определяет мастер как пошаговый ввод данных в заданном порядке, где следующие шаги могут зависеть от предыдущих ответов. Для арбитражника у квиза есть ещё одна польза: ответы уходят оператору колл-центра и в трекер, и по ним видно, какие посетители дают апрув.

Квиз для сайта: три шага квиз-формы на экране телефона, от выбора варианта до имени и телефона
Квиз-форма из статьи на телефоне: вопросы по одному, в конце имя и телефон

Когда квиз поднимает конверсию лендинга, а когда мешает

Универсальной цифры «квиз даёт плюс N%» нет: всё решает тест на вашем трафике. Зато понятно, от чего зависит результат. NN/g перечисляет плюсы мастеров: на каждом экране меньше полей, человек не пугается длинной формы, меньше ошибается и проходит только нужный ему путь. Минусы тоже названы: больше кликов, неудобно тем, кто знает, чего хочет, и тем, кто проходит процесс много раз.

Квиз скорее поможет, если:

  • трафик холодный и человек ещё не решил, нужен ли ему товар: вопросы вовлекают его в выбор;
  • у оффера есть варианты (размер, для кого, какая проблема), и оператору проще продать, зная ответы заранее;
  • вы хотите понять, какие ответы дают апрув, и резать трафик по ним.

Квиз скорее помешает, если:

  • посетитель пришёл готовым купить: каждый лишний шаг отделяет его от кнопки заказа;
  • вопросы ничего не дают оператору и задаются ради «вовлечения»;
  • на последнем шаге стоит таймер, который перезапускается. W3C в руководстве по многошаговым формам советует по возможности не ставить ограничение по времени на заполнение формы.

Наш опыт 2024 года: мы ставили квиз на нутра-лендинги вместо колёс фортуны и «дверей». Конверсия в заявку была чуть ниже, чем у обычной формы, а апрув выше: на последнем шаге квиз подчёркивал, что акционная цена действует только на одну упаковку. Это наблюдение по нашим офферам, а не правило. Проверяйте на своих: сделайте две копии лендинга и разделите трафик сплит-тестом лендингов в Keitaro. Сравнивайте CR, апрув и EPC: как их считать, разобрано в статье про метрики арбитража.

Где сделать квиз: сервисы и свой код

ВариантЦенаПлюсыМинусы
Marquiz«Бесплатный» 0 ₽ на 10 заявок в месяц, «Старт» 790 ₽ на 30 заявок, «Плюс» от 1 790 ₽конструктор, шаблоны, интеграциитариф считается по заявкам, квиз работает на стороне сервиса
Тильда, блок QZ101сайт на Тильде, тариф Personal 500 ₽ в месяц при оплате за год или 750 ₽ помесячноветвление вопросов, виртуальный консультант, отправка в сервисы приёма данныхтолько для сайтов на Тильде
Свой код (ниже)бесплатнолежит в архиве лендинга, работает в Keitaro, ответы уходят в сеть и в sub_idвопросы правите в HTML сами
Таблица прокручивается вбок

Цены сняты со страниц тарифов 1 октября 2026 года. Для арбитражного лендинга у конструктора два минуса: форма грузится со стороннего домена, а связку с subid клика и API партнёрской сети придётся собирать через интеграции сервиса. Свой код лежит в архиве лендинга и отправляет заявку тем же api.php, что и обычная форма.

Квиз-форма: готовый код

Каждый вопрос это <fieldset> с заголовком <legend>, а варианты это обычные радиокнопки и галочки внутри <label>. Поэтому клавиатура и скринридер работают без дополнительных настроек: стрелки выбирают вариант, пробел ставит галочку, скринридер читает вопрос вместе с вариантом. Если JavaScript не загрузился, форма показывает все вопросы сразу и отправляется как обычная.

Что умеет квиз:

  • показывает по одному вопросу со счётчиком «Шаг 2 из 4», счётчик зачитывается скринридером;
  • Enter переводит на следующий шаг, а не отправляет форму; кнопка «Назад» сохраняет ответы;
  • клик мышью или пальцем по варианту сразу открывает следующий вопрос, стрелки на клавиатуре так не делают;
  • не пускает дальше без ответа, в группе галочек требует хотя бы одну, в телефоне ждёт от 7 до 15 цифр;
  • на телефоне варианты высотой 48 px, крупные кнопки и поля со шрифтом 16 px;
  • собирает ответы в текст для оператора и блокирует кнопку отправки после первого клика, чтобы не плодить дубли лидов.

HTML: вставьте вместо старой формы

<form class="kq" action="api.php" method="post">
  <h2 class="kq__title">Подберём уход за ногами за 3 вопроса</h2>
  <div class="kq__top" hidden>
    <p class="kq__count" aria-live="polite">Шаг 1 из 4</p>
    <div class="kq__bar" aria-hidden="true"><span></span></div>
  </div>

  <fieldset class="kq__step">
    <legend>Для кого подбираем?</legend>
    <label class="kq__opt"><input type="radio" name="q1" value="self" required> Для себя</label>
    <label class="kq__opt"><input type="radio" name="q1" value="parents"> Для родителей</label>
    <label class="kq__opt"><input type="radio" name="q1" value="gift"> В подарок</label>
  </fieldset>

  <fieldset class="kq__step" data-min="1">
    <legend>Что беспокоит к вечеру? <span class="kq__hint">Можно выбрать несколько</span></legend>
    <label class="kq__opt"><input type="checkbox" name="q2[]" value="tired"> Усталость ног</label>
    <label class="kq__opt"><input type="checkbox" name="q2[]" value="heavy"> Тяжесть в ногах</label>
    <label class="kq__opt"><input type="checkbox" name="q2[]" value="dry"> Сухая кожа стоп</label>
    <label class="kq__opt"><input type="checkbox" name="q2[]" value="care"> Ничего, нужен уход</label>
  </fieldset>

  <fieldset class="kq__step">
    <legend>Когда удобнее принять звонок?</legend>
    <label class="kq__opt"><input type="radio" name="q3" value="morning" required> Утром, до 12:00</label>
    <label class="kq__opt"><input type="radio" name="q3" value="day"> Днём, 12:00-17:00</label>
    <label class="kq__opt"><input type="radio" name="q3" value="evening"> Вечером, после 17:00</label>
  </fieldset>

  <fieldset class="kq__step">
    <legend>Куда позвонить? <span class="kq__hint">Оператор расскажет о составе и условиях доставки</span></legend>
    <label class="kq__field">Имя
      <input type="text" name="name" autocomplete="name" required minlength="2" maxlength="60">
    </label>
    <label class="kq__field">Телефон
      <input type="tel" name="phone" autocomplete="tel" inputmode="tel" required maxlength="20">
    </label>
  </fieldset>

  <input type="hidden" name="subid" value="{subid}">
  <input type="hidden" name="answers" value="">

  <div class="kq__nav">
    <button type="button" class="kq__back" hidden>Назад</button>
    <button type="button" class="kq__next" hidden>Далее</button>
    <button type="submit" class="kq__send">Жду звонка</button>
  </div>
</form>

В value вариантов пишите короткие латинские коды: они уйдут в Keitaro. Подписи могут быть на любом языке. Порядок шагов любой: если хотите, как в нашей версии 2024 года, спрашивать имя и телефон первыми, просто переставьте последний <fieldset> наверх.

CSS: файл quiz.css

.kq{max-width:560px;margin:0 auto;padding:20px 16px;background:#fff;color:#1f2328;border-radius:14px;box-shadow:0 2px 12px rgba(0,0,0,.08);font:17px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;box-sizing:border-box}
.kq *,.kq *::before,.kq *::after{box-sizing:border-box}
.kq [hidden]{display:none!important}
.kq__title{margin:0 0 12px;font-size:24px;line-height:1.25}
.kq__count{margin:0 0 6px;font-size:14px;color:#4b5563}
.kq__bar{height:8px;margin-bottom:16px;border-radius:4px;background:#e5e7eb;overflow:hidden}
.kq__bar span{display:block;width:0;height:100%;background:#166534;transition:width .3s}
.kq__step{min-width:0;margin:0 0 16px;padding:0;border:0}
.kq__step legend{margin-bottom:12px;padding:0;font-size:20px;font-weight:700}
.kq__hint{display:block;font-size:14px;font-weight:400;color:#4b5563}
.kq__opt{display:flex;align-items:center;gap:10px;min-height:48px;margin-bottom:8px;padding:10px 14px;border:1px solid #d1d5db;border-radius:10px;cursor:pointer;-webkit-tap-highlight-color:transparent}
.kq__opt:has(input:checked){border-color:#166534;background:#f0fdf4}
.kq__opt:has(input:focus-visible){outline:3px solid #1d4ed8;outline-offset:2px}
.kq__opt input{flex:none;width:20px;height:20px;margin:0;accent-color:#166534}
.kq__field{display:block;margin-bottom:12px;font-weight:600}
.kq__field input{display:block;width:100%;margin-top:6px;padding:12px;border:1px solid #9ca3af;border-radius:10px;font:inherit;font-size:16px;font-weight:400}
.kq__nav{display:flex;gap:10px}
.kq__nav button{min-height:48px;padding:12px 18px;border-radius:10px;font:inherit;font-weight:700;cursor:pointer}
.kq__back{border:1px solid #9ca3af;background:#fff;color:#1f2328}
.kq__next,.kq__send{flex:1;border:0;background:#166534;color:#fff}
.kq__send:disabled{opacity:.6;cursor:wait}
.kq button:focus-visible,.kq__field input:focus-visible{outline:3px solid #1d4ed8;outline-offset:2px}
@media (prefers-reduced-motion:reduce){.kq__bar span{transition:none}}

JavaScript: файл quiz.js

/* quiz.js: пошаговая квиз-форма. Без JavaScript форма показывает все вопросы сразу и отправляется как обычная */
(() => {
  document.querySelectorAll('form.kq').forEach((form) => {
    const steps = [...form.querySelectorAll('.kq__step')];
    const count = form.querySelector('.kq__count');
    const bar = form.querySelector('.kq__bar span');
    const back = form.querySelector('.kq__back');
    const next = form.querySelector('.kq__next');
    const send = form.querySelector('.kq__send');
    const answers = form.querySelector('input[name="answers"]');
    const d = form.dataset;
    let cur = 0;
    let lastPointer = 0;

    const fields = (step) => [...step.querySelectorAll('input, select, textarea')];

    // Свои проверки поверх стандартных: минимум ответов в группе галочек и цифры в телефоне
    function setChecks(step) {
      const min = Number(step.dataset.min || 0);
      if (min) {
        const boxes = step.querySelectorAll('input[type="checkbox"]');
        const checked = step.querySelectorAll('input[type="checkbox"]:checked').length;
        boxes[0].setCustomValidity(checked >= min ? '' : (d.errorChoice || 'Выберите хотя бы один вариант'));
      }
      const phone = step.querySelector('input[type="tel"]');
      if (phone) {
        const digits = phone.value.replace(/\D/g, '').length;
        const ok = phone.value === '' || (digits >= 7 && digits <= 15);
        phone.setCustomValidity(ok ? '' : (d.errorPhone || 'Проверьте номер телефона'));
      }
    }

    const stepOk = (step) => { setChecks(step); return fields(step).every((f) => f.checkValidity()); };

    function report(step) {
      setChecks(step);
      const bad = fields(step).find((f) => !f.checkValidity());
      if (bad) bad.reportValidity();
      return !bad;
    }

    function show(i, focus) {
      cur = i;
      steps.forEach((s, k) => { s.hidden = k !== i; });
      const last = i === steps.length - 1;
      back.hidden = i === 0;
      next.hidden = last;
      send.hidden = !last;
      count.textContent = (d.stepText || 'Шаг {n} из {total}').replace('{n}', i + 1).replace('{total}', steps.length);
      bar.style.width = ((i + 1) / steps.length) * 100 + '%';
      if (focus) {
        const f = steps[i].querySelector('input:checked') || fields(steps[i])[0];
        if (f) f.focus();
      }
    }

    function summary() {
      return steps.map((step) => {
        const picked = [...step.querySelectorAll('input:checked')].map((i) => i.parentElement.textContent.trim());
        if (!picked.length) return '';
        const legend = step.querySelector('legend').cloneNode(true);
        legend.querySelectorAll('.kq__hint').forEach((h) => h.remove());
        return legend.textContent.trim() + ' ' + picked.join(', ');
      }).filter(Boolean).join('; ').slice(0, 500);
    }

    form.noValidate = true; // проверяем сами по шагам, иначе браузер ругается на скрытые поля
    form.querySelector('.kq__top').hidden = false;
    show(0, false);

    next.addEventListener('click', () => { if (report(steps[cur])) show(cur + 1, true); });
    back.addEventListener('click', () => show(cur - 1, true));

    // Enter на промежуточном шаге ведёт дальше, а не отправляет форму
    form.addEventListener('keydown', (e) => {
      if (e.key !== 'Enter' || e.target.tagName === 'BUTTON' || e.target.tagName === 'TEXTAREA') return;
      if (cur < steps.length - 1) { e.preventDefault(); next.click(); }
    });

    // Клик мышью или пальцем по варианту сразу открывает следующий шаг. Стрелки на клавиатуре так не делают
    form.addEventListener('pointerdown', () => { lastPointer = Date.now(); });
    form.addEventListener('change', (e) => {
      setChecks(steps[cur]);
      if (e.target.type === 'radio' && Date.now() - lastPointer < 1000 && cur < steps.length - 1) {
        const at = cur;
        setTimeout(() => { if (cur === at) next.click(); }, 250);
      }
    });

    form.addEventListener('submit', (e) => {
      const badStep = steps.findIndex((s) => !stepOk(s));
      if (badStep !== -1) {
        e.preventDefault();
        show(badStep, false);
        report(steps[badStep]);
        return;
      }
      answers.value = summary();
      send.disabled = true; // защита от двойного клика
    });

    // Кнопка «Назад» в браузере возвращает страницу из кеша: включаем кнопку отправки снова
    window.addEventListener('pageshow', () => { send.disabled = false; });
  });
})();

Подключение: <link rel="stylesheet" href="quiz.css"> в <head> и <script src="quiz.js" defer></script> перед </body>. Для других языков задайте тексты атрибутами формы: data-step-text="Paso {n} de {total}", data-error-choice и data-error-phone. Остальной текст лендинга переводите вместе с вопросами квиза.

Как отправить ответы в api.php и Keitaro

Проверки в браузере помогают посетителю, но не защищают сервер от произвольного POST-запроса. В api.php отдельно проверяйте обязательные ответы, допустимые коды вариантов и формат контактов. Данные о здоровье из вопросов не передавайте рекламным системам как параметры событий.

Квиз отправляет в api.php поля name, phone, subid, коды ответов q1, q2[], q3 и текст answers. Сам api.php берите из статьи про вашу партнёрскую сеть, например про интеграцию Leadbit с Keitaro. К нему добавляются две вещи.

  1. Ответы оператору. Если API сети принимает комментарий (в наших разборах поле comment есть у Leadbit, RocketProfit, Shakes.pro и AlterCPA), передайте в него quiz_comment(). Оператор увидит: «Для кого подбираем? Для родителей; Что беспокоит к вечеру? Усталость ног, Тяжесть в ногах; Когда удобнее принять звонок? Днём, 12:00-17:00».
  2. Ответы в трекер. Функция quiz_to_keitaro() записывает коды ответов в параметры клика sub_id_7, sub_id_8, sub_id_9. Для этого Keitaro принимает запрос ?_update_tokens=1&sub_id=…: так описано в документации об обновлении параметров. Изменения видны только в отчётах, на ссылки они не влияют. После этого в отчёте Keitaro можно сгруппировать конверсии по sub_id_8 и увидеть апрув по каждому ответу.

Файл quiz-keitaro.php

<?php
// quiz-keitaro.php: ответы квиза для api.php. Подключите в начале api.php:
// require __DIR__ . '/quiz-keitaro.php';

const KT_TRACKER    = 'https://ВАШ_ТРЕКЕР';  // адрес Keitaro без слеша в конце
const QUIZ_TO_SUBID = ['q1' => 'sub_id_7', 'q2' => 'sub_id_8', 'q3' => 'sub_id_9']; // вопрос => параметр клика

// Ответы текстом для поля comment партнёрской сети, если её API принимает комментарий
function quiz_comment(): string
{
    $text = trim(strip_tags((string)($_POST['answers'] ?? '')));
    if ($text === '') { // форма ушла без JavaScript: собираем ответы из кодов
        $parts = [];
        foreach (array_keys(QUIZ_TO_SUBID) as $q) {
            $v = $_POST[$q] ?? '';
            $parts[] = $q . '=' . (is_array($v) ? implode(',', $v) : (string)$v);
        }
        $text = implode('; ', $parts);
    }
    return mb_substr($text, 0, 500);
}

// Записывает коды ответов в sub_id_7..9 клика: обновление параметров по документации Keitaro.
// Вызывайте в самом конце api.php, после header('Location: ...'), чтобы посетитель не ждал трекер.
function quiz_to_keitaro(): void
{
    $subid = (string)($_POST['subid'] ?? '');
    if (!preg_match('/^[A-Za-z0-9_-]{4,64}$/', $subid)) {
        return; // subid пустой или макрос {subid} не подставился: обновлять нечего
    }
    $params = ['_update_tokens' => 1, 'sub_id' => $subid];
    foreach (QUIZ_TO_SUBID as $q => $token) {
        $v = $_POST[$q] ?? '';
        $v = is_array($v) ? implode(',', $v) : (string)$v;
        if (preg_match('/^[a-z0-9_,-]{1,100}$/i', $v)) {
            $params[$token] = $v; // в трекер уходят только коды ответов, без текста
        }
    }
    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request(); // отдаём редирект посетителю, запрос в трекер идёт уже без него
    }
    $ch = curl_init(KT_TRACKER . '/?' . http_build_query($params));
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 2,
        CURLOPT_TIMEOUT        => 2,
    ]);
    curl_exec($ch);
}

Что дописать в api.php

// 1. В начале api.php
require __DIR__ . '/quiz-keitaro.php';

// 2. В массив заявки для сети, если её API принимает комментарий
'comment' => quiz_comment(),

// 3. В самом конце, после редиректа на страницу «Спасибо»
header('Location: ' . ($ok ? 'success.php' : 'error.php'));
quiz_to_keitaro();
exit;

Почему запрос в трекер стоит в самом конце. У PHP в локальных лендингах Keitaro по требованиям есть 3 секунды, и почти всё это время может уйти на запрос в сеть. На серверах с PHP-FPM функция fastcgi_finish_request() сначала отдаёт посетителю редирект на страницу «Спасибо», а трекер обновляется уже без него. Номера sub_id выберите свободные: посмотрите, какие параметры уже заняты метками источника в настройках кампании.

Установка по шагам

Все файлы из статьи одним архивом: newform.zip (quiz.html, quiz.css, quiz.js и обработчик quiz-keitaro.php, версия от 1 октября 2026).

  1. Вставьте HTML квиза на место старой формы и замените вопросы и варианты под свой оффер.
  2. Подключите quiz.css и quiz.js.
  3. Скрытое поле subid оставьте со значением {subid}, если лендинг загружен в Keitaro: трекер подставит ID клика в index.html или index.php. Если лендинг на своём хостинге, передавайте subid в адресе, как в статьях про интеграции с сетями.
  4. Положите quiz-keitaro.php рядом с api.php, впишите адрес трекера в KT_TRACKER и свободные параметры в QUIZ_TO_SUBID, допишите три строки в api.php.
  5. Пройдите квиз только с клавиатуры и с телефона, отправьте тестовую заявку. Проверьте комментарий в кабинете сети и значения sub_id_7-sub_id_9 у клика в логе кликов Keitaro.

Телефон на последнем шаге можно усилить маской с кодом страны: готовый вариант для Keitaro есть в статье про маску ввода телефона.

Что исправлено по сравнению с версией 2024 года

Мы прогнали старую форму из архива 2024 года в Chromium и нашли ошибки, которые теряли данные или ломали путь посетителя:

  • Enter в поле имени сразу отправлял заявку целиком, минуя шаги. В поле телефона заранее стояло «+7», поэтому браузер считал его заполненным, и в сеть уходил лид с телефоном «+7».
  • По той же причине на шаге телефона кнопка «Далее» была активна, пока номер не введён.
  • Выбранные симптомы не попадали в заявку: это были кнопки без полей формы.
  • При переходе на последний шаг скрипт падал с ошибкой «Cannot set properties of undefined».
  • В HTML-файле стоял код <?php echo $_GET['pxl']; ?>. В файле .html PHP не выполняется, и в заявку уходил сам этот текст.
  • Таймер «скидки» после нуля начинался заново с 5:00. Таймер и фразу об одобренной скидке 100% мы из примера убрали: условия акции у каждого оффера свои, пишите их на последнем шаге сами.
  • Кнопки симптомов не сообщали скринридеру, выбраны ли они, а их значения были на испанском при русских подписях.

Если вы ищете другие способы помочь посетителю дойти до заявки без оператора, посмотрите наш ИИ-консультант для лендинга: он отвечает на вопросы по тексту страницы и ведёт к форме.

Частые вопросы

Что такое квиз на сайте простыми словами?

Это опрос из нескольких вопросов с вариантами ответов, в конце которого посетитель оставляет контакты. По сути та же форма заявки, только разбитая на шаги.

Сколько вопросов делать в квизе?

Столько, сколько реально пригодится оператору или для анализа трафика. Каждый шаг добавляет клик, поэтому вопросы «для вовлечения» лучше убрать. В нашем примере три вопроса и шаг с контактами.

Телефон спрашивать в начале или в конце?

В типовом квизе контакты идут последним шагом. В нашей версии 2024 года имя и телефон шли первыми. Код поддерживает любой порядок, а что лучше для вашего оффера, покажет сплит-тест.

Можно ли сделать квиз бесплатно?

Да. В Marquiz есть бесплатный тариф на 10 заявок в месяц, а код из этой статьи бесплатный без ограничений.

Как сделать квиз на Тильде?

В библиотеке блоков откройте раздел «Форма» и выберите блок QZ101 или одну из его вариаций QZ101A-QZ101F. Шаги, ветвление и отправка данных настраиваются в контенте блока.

Работает ли квиз без JavaScript?

Да. Без скрипта форма показывает все вопросы на одной странице и отправляется как обычная, а quiz_comment() собирает ответы из кодов вариантов.

Редакция BoostClicks

Материалы о настройке рекламы, трекинге и автоматизации: инструкции, примеры кода и расчёты с указанием условий проверки.