Multi-Step Survey Form

A 3-step survey with a progress bar. Breaks long questionnaires into digestible pages, reducing drop-off and improving completion rates.

Before you start
Before using the Form Template below, you need a Formtorch endpoint.
Don't have one yet? Sign up now and get one in less than 5 minutes.

Add your Formtorch endpoint

Paste your form ID and the code below updates instantly.

survey.html
<section class="ft-survey-wrap">
  <!-- Progress bar -->
  <div
    class="ft-progress"
    role="progressbar"
    aria-valuenow="1"
    aria-valuemin="1"
    aria-valuemax="3"
  >
    <div class="ft-progress-bar" id="ft-bar"></div>
  </div>
  <p class="ft-step-label">Step <span id="ft-step-current">1</span> of 3</p>

  <form
    id="ft-survey"
    action="https://formtorch.com/f/abc123"
    method="POST"
    accept-charset="UTF-8"
  >
    <input type="hidden" name="_formName" value="Customer Survey" />

    <!-- Step 1: About you -->
    <fieldset class="ft-step" id="ft-step-1">
      <legend class="ft-legend">About you</legend>

      <div class="ft-field">
        <label for="ft-role" class="ft-label"
          >What best describes your role?</label
        >
        <select id="ft-role" name="role" required>
          <option value="" disabled selected>Choose one&hellip;</option>
          <option value="developer">Developer / Engineer</option>
          <option value="designer">Designer</option>
          <option value="product">Product Manager</option>
          <option value="marketer">Marketer</option>
          <option value="founder">Founder / Owner</option>
          <option value="other">Other</option>
        </select>
      </div>

      <div class="ft-actions ft-actions--end">
        <button
          type="button"
          class="ft-btn ft-btn--primary"
          onclick="ftNext(1)"
        >
          Next &rarr;
        </button>
      </div>
    </fieldset>

    <!-- Step 2: Satisfaction -->
    <fieldset class="ft-step ft-hidden" id="ft-step-2">
      <legend class="ft-legend">Your experience</legend>

      <div class="ft-field">
        <label class="ft-label">How satisfied are you overall?</label>
        <div class="ft-radio-group">
          <label class="ft-radio"
            ><input
              type="radio"
              name="satisfaction"
              value="very_satisfied"
              required
            />
            Very satisfied</label
          >
          <label class="ft-radio"
            ><input type="radio" name="satisfaction" value="satisfied" />
            Satisfied</label
          >
          <label class="ft-radio"
            ><input type="radio" name="satisfaction" value="neutral" />
            Neutral</label
          >
          <label class="ft-radio"
            ><input type="radio" name="satisfaction" value="dissatisfied" />
            Dissatisfied</label
          >
          <label class="ft-radio"
            ><input
              type="radio"
              name="satisfaction"
              value="very_dissatisfied"
            />
            Very dissatisfied</label
          >
        </div>
      </div>

      <div class="ft-field">
        <label for="ft-feature" class="ft-label"
          >Which feature do you use most?</label
        >
        <select id="ft-feature" name="feature" required>
          <option value="" disabled selected>Choose one&hellip;</option>
          <option value="form_builder">Form builder</option>
          <option value="notifications">Email notifications</option>
          <option value="integrations">Integrations</option>
          <option value="analytics">Analytics</option>
          <option value="spam_protection">Spam protection</option>
        </select>
      </div>

      <div class="ft-actions">
        <button type="button" class="ft-btn ft-btn--ghost" onclick="ftPrev(2)">
          &larr; Back
        </button>
        <button
          type="button"
          class="ft-btn ft-btn--primary"
          onclick="ftNext(2)"
        >
          Next &rarr;
        </button>
      </div>
    </fieldset>

    <!-- Step 3: Feedback -->
    <fieldset class="ft-step ft-hidden" id="ft-step-3">
      <legend class="ft-legend">Final thoughts</legend>

      <div class="ft-field">
        <label for="ft-feedback" class="ft-label">
          What could we do better? <span class="ft-optional">(optional)</span>
        </label>
        <textarea
          id="ft-feedback"
          name="feedback"
          rows="4"
          placeholder="Your feedback helps us improve&hellip;"
        ></textarea>
      </div>

      <div class="ft-actions">
        <button type="button" class="ft-btn ft-btn--ghost" onclick="ftPrev(3)">
          &larr; Back
        </button>
        <button type="submit" class="ft-btn ft-btn--primary">
          Submit survey
        </button>
      </div>
    </fieldset>
  </form>
</section>

<script>
  var FT_TOTAL = 3;

  function ftSetStep(n) {
    for (var i = 1; i <= FT_TOTAL; i++) {
      var el = document.getElementById("ft-step-" + i);
      if (el) el.classList.toggle("ft-hidden", i !== n);
    }
    document.getElementById("ft-step-current").textContent = n;
    var pct = ((n - 1) / (FT_TOTAL - 1)) * 100;
    document.getElementById("ft-bar").style.width =
      (n === FT_TOTAL ? 100 : pct) + "%";
    document.querySelector(".ft-progress").setAttribute("aria-valuenow", n);
  }

  function ftNext(current) {
    var step = document.getElementById("ft-step-" + current);
    var inputs = step.querySelectorAll("input,select,textarea");
    var valid = true;
    inputs.forEach(function (el) {
      if (!el.checkValidity()) {
        el.reportValidity();
        valid = false;
      }
    });
    if (valid && current < FT_TOTAL) ftSetStep(current + 1);
  }

  function ftPrev(current) {
    if (current > 1) ftSetStep(current - 1);
  }
</script>

<style>
  .ft-survey-wrap {
    box-sizing: border-box;
    width: 100%;
    max-width: 480px;
    font-family: Inter, system-ui, sans-serif;
    font-size: 14px;
    line-height: 1.5;

    --ft-accent-hue: 210;
    --ft-accent: hsl(var(--ft-accent-hue) 85% 52%);
    --ft-border: #e2e8f0;
    --ft-border-hover: #cbd5e1;
    --ft-text: #0f172a;
    --ft-label: #374151;
    --ft-hint: #6b7280;
    --ft-optional: #9ca3af;
    --ft-placeholder: #9ca3af;
    --ft-bg-field: #ffffff;
    --ft-bg-hover: #f8fafc;
    --ft-progress-bg: #e2e8f0;
  }

  .ft-survey-wrap * {
    box-sizing: border-box;
  }

  /* Progress */
  .ft-progress {
    height: 6px;
    border-radius: 99px;
    background: var(--ft-progress-bg);
    overflow: hidden;
    margin-bottom: 8px;
  }

  .ft-progress-bar {
    height: 100%;
    width: 0%;
    border-radius: 99px;
    background: var(--ft-accent);
    transition: width 300ms ease;
  }

  .ft-step-label {
    margin: 0 0 20px;
    font-size: 12px;
    color: var(--ft-hint);
    text-align: right;
  }

  /* Steps */
  .ft-step {
    border: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .ft-hidden {
    display: none !important;
  }

  .ft-legend {
    font-size: 17px;
    font-weight: 700;
    color: var(--ft-text);
    margin-bottom: 4px;
    padding: 0;
    float: left;
    width: 100%;
  }

  .ft-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .ft-label {
    font-size: 13px;
    font-weight: 500;
    color: var(--ft-label);
  }

  .ft-optional {
    font-weight: 400;
    color: var(--ft-optional);
  }

  .ft-survey-wrap input[type="text"],
  .ft-survey-wrap select,
  .ft-survey-wrap textarea {
    width: 100%;
    border: 1px solid var(--ft-border);
    border-radius: 8px;
    background: var(--ft-bg-field);
    color: var(--ft-text);
    font: inherit;
    padding: 10px 12px;
    outline: none;
    transition:
      border-color 120ms ease,
      box-shadow 120ms ease;
    appearance: none;
  }

  .ft-survey-wrap select {
    height: 42px;
    padding-top: 0;
    padding-bottom: 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236b7280' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    padding-right: 32px;
  }

  .ft-survey-wrap textarea {
    resize: vertical;
    min-height: 88px;
  }

  .ft-survey-wrap input::placeholder,
  .ft-survey-wrap textarea::placeholder {
    color: var(--ft-placeholder);
  }

  .ft-survey-wrap input:hover,
  .ft-survey-wrap select:hover,
  .ft-survey-wrap textarea:hover {
    border-color: var(--ft-border-hover);
    background: var(--ft-bg-hover);
  }

  .ft-survey-wrap input:focus-visible,
  .ft-survey-wrap select:focus-visible,
  .ft-survey-wrap textarea:focus-visible {
    border-color: var(--ft-accent);
    box-shadow: 0 0 0 3px hsl(var(--ft-accent-hue) 85% 52% / 0.15);
    background: var(--ft-bg-field);
  }

  /* Radio */
  .ft-radio-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
  }

  .ft-radio {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--ft-text);
    cursor: pointer;
  }

  .ft-radio input[type="radio"] {
    width: 16px;
    height: 16px;
    accent-color: var(--ft-accent);
    cursor: pointer;
  }

  /* Actions */
  .ft-actions {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 4px;
  }

  .ft-actions--end {
    justify-content: flex-end;
  }

  .ft-btn {
    appearance: none;
    border-radius: 8px;
    height: 42px;
    padding: 0 20px;
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition:
      filter 120ms ease,
      transform 120ms ease;
  }

  .ft-btn--primary {
    border: 0;
    background: var(--ft-accent);
    color: #fff;
  }

  .ft-btn--primary:hover {
    filter: brightness(1.08);
  }
  .ft-btn--primary:active {
    transform: translateY(1px);
  }

  .ft-btn--ghost {
    border: 1px solid var(--ft-border);
    background: transparent;
    color: var(--ft-text);
  }

  .ft-btn--ghost:hover {
    border-color: var(--ft-border-hover);
    background: var(--ft-bg-hover);
  }

  .ft-btn:focus-visible {
    box-shadow: 0 0 0 3px hsl(var(--ft-accent-hue) 85% 52% / 0.25);
    outline: none;
  }

  .ft-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
</style>

How it works

Create a Formtorch endpoint

Sign up and create a new form endpoint in the dashboard. You will get a unique URL in seconds.

Create endpoint

Paste it into your form

Set your form's action attribute to your endpoint URL. No other changes required.

Deploy and start receiving submissions

Every submission is stored in your dashboard, triggers email notifications, and can fire webhooks to any service.

Frequently asked questions

No. Formtorch works with standard HTML forms via the action attribute. No JavaScript required for basic submissions.

Start in less than 5 minutes

Create your endpoint, paste it into any template, and you are live.