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.
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-demo" aria-label="Multi-step survey form demo">
<div class="ft-window">
<div class="ft-window-bar" aria-hidden="true">
<span></span>
<span></span>
<span></span>
</div>
<form
id="ft-survey"
class="ft-form"
action="https://formtorch.com/f/abc123"
method="POST"
accept-charset="UTF-8"
>
<div class="ft-icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M8 6h13" />
<path d="M8 12h13" />
<path d="M8 18h13" />
<path d="M3 6h.01" />
<path d="M3 12h.01" />
<path d="M3 18h.01" />
</svg>
</div>
<div class="ft-form-heading">
<h2>Multi-step Survey Form</h2>
<p>A guided survey flow broken into simple steps.</p>
</div>
<div class="ft-progress-row">
<span class="ft-progress-label"
>Step <span id="ft-step-current">1</span> of 4</span
>
<div class="ft-step-dots" aria-hidden="true">
<span class="ft-step-dot" data-dot="1">1</span>
<span class="ft-step-line" data-line="1"></span>
<span class="ft-step-dot" data-dot="2">2</span>
<span class="ft-step-line" data-line="2"></span>
<span class="ft-step-dot" data-dot="3">3</span>
<span class="ft-step-line" data-line="3"></span>
<span class="ft-step-dot" data-dot="4">4</span>
</div>
</div>
<div
class="ft-progress-track"
role="progressbar"
aria-valuenow="1"
aria-valuemin="1"
aria-valuemax="4"
aria-label="Survey progress"
>
<div class="ft-progress-bar" id="ft-bar"></div>
</div>
<input type="hidden" name="_formName" value="Customer Survey" />
<!-- Step 1: About you -->
<fieldset class="ft-step" id="ft-step-1">
<legend class="ft-sr-only">About you</legend>
<div class="ft-field">
<label for="ft-name">Name</label>
<div class="ft-control">
<svg aria-hidden="true" viewBox="0 0 24 24">
<circle cx="12" cy="8" r="4" />
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
</svg>
<input
id="ft-name"
type="text"
name="name"
placeholder="Your name"
required
/>
</div>
</div>
<div class="ft-field">
<label for="ft-email">Email</label>
<div class="ft-control">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M4 6h16v12H4z" />
<path d="m4 7 8 6 8-6" />
</svg>
<input
id="ft-email"
type="email"
name="email"
placeholder="you@example.com"
required
/>
</div>
</div>
<div class="ft-field">
<label for="ft-usecase">What are you using Formtorch for?</label>
<div class="ft-control">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 4h6l4 4v12H8Z" />
<path d="M14 4v4h4" />
<path d="M9.5 12h5" />
<path d="M9.5 16h5" />
</svg>
<select id="ft-usecase" name="use_case" required>
<option value="" disabled selected>Select an option</option>
<option value="contact">Contact forms</option>
<option value="leads">Lead generation</option>
<option value="feedback">Customer feedback</option>
<option value="rsvp">Event RSVPs</option>
<option value="waitlist">Waitlist / early access</option>
<option value="other">Other</option>
</select>
</div>
</div>
<div class="ft-actions">
<button
type="button"
class="ft-btn ft-btn--ghost"
data-prev="1"
disabled
>
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
Back
</button>
<button type="button" class="ft-btn ft-btn--primary" data-next="1">
Next Step
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</button>
</div>
</fieldset>
<!-- Step 2: Satisfaction -->
<fieldset class="ft-step ft-hidden" id="ft-step-2">
<legend class="ft-sr-only">Your experience</legend>
<div class="ft-field">
<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-actions">
<button type="button" class="ft-btn ft-btn--ghost" data-prev="2">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
Back
</button>
<button type="button" class="ft-btn ft-btn--primary" data-next="2">
Next Step
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</button>
</div>
</fieldset>
<!-- Step 3: Feature usage -->
<fieldset class="ft-step ft-hidden" id="ft-step-3">
<legend class="ft-sr-only">Feature usage</legend>
<div class="ft-field">
<label for="ft-feature">Which feature do you use most?</label>
<div class="ft-control">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M8 4h6l4 4v12H8Z" />
<path d="M14 4v4h4" />
<path d="M9.5 12h5" />
<path d="M9.5 16h5" />
</svg>
<select id="ft-feature" name="feature" required>
<option value="" disabled selected>Select an option</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>
<div class="ft-actions">
<button type="button" class="ft-btn ft-btn--ghost" data-prev="3">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
Back
</button>
<button type="button" class="ft-btn ft-btn--primary" data-next="3">
Next Step
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="M5 12h14" />
<path d="m12 5 7 7-7 7" />
</svg>
</button>
</div>
</fieldset>
<!-- Step 4: Feedback -->
<fieldset class="ft-step ft-hidden" id="ft-step-4">
<legend class="ft-sr-only">Final thoughts</legend>
<div class="ft-field">
<label for="ft-feedback">
What could we do better? <em>(optional)</em>
</label>
<textarea
id="ft-feedback"
name="feedback"
rows="4"
placeholder="Your feedback helps us improve..."
></textarea>
</div>
<div class="ft-actions">
<button type="button" class="ft-btn ft-btn--ghost" data-prev="4">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="m12 19-7-7 7-7" />
<path d="M19 12H5" />
</svg>
Back
</button>
<button type="submit" class="ft-btn ft-btn--primary">
<svg aria-hidden="true" viewBox="0 0 24 24">
<path d="m22 2-7 20-4-9-9-4Z" />
<path d="M22 2 11 13" />
</svg>
Submit Survey
</button>
</div>
</fieldset>
<p id="ft-status" class="ft-status" aria-live="polite">
<svg aria-hidden="true" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" />
<path d="m9 12 2 2 4-4" />
</svg>
<span>Progress is saved as you go.</span>
</p>
</form>
</div>
</section>
<style>
html,
body {
margin: 0;
padding: 0;
background: #05070d;
}
.ft-survey-demo {
position: relative;
box-sizing: border-box;
width: 100%;
min-height: 100vh;
padding: clamp(24px, 5vw, 64px);
display: grid;
place-items: center;
font-family:
Inter,
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
color: #f8fafc;
background:
radial-gradient(circle at 22% 18%, rgb(37 99 235 / 0.2), transparent 32%),
radial-gradient(
circle at 78% 75%,
rgb(37 99 235 / 0.18),
transparent 30%
),
#05070d;
}
.ft-survey-demo,
.ft-survey-demo * {
box-sizing: border-box;
}
.ft-survey-demo::before,
.ft-survey-demo::after {
content: "";
position: absolute;
z-index: 0;
width: clamp(90px, 16vw, 130px);
height: clamp(130px, 20vw, 190px);
pointer-events: none;
opacity: 0.45;
background-image: radial-gradient(
rgb(37 99 235 / 0.9) 1.5px,
transparent 1.5px
);
background-size: 20px 20px;
}
.ft-survey-demo::before {
top: 9%;
left: 5%;
}
.ft-survey-demo::after {
bottom: 9%;
right: 5%;
}
.ft-window {
position: relative;
z-index: 1;
width: min(100%, 560px);
overflow: hidden;
border: 1px solid rgb(148 163 184 / 0.22);
border-radius: 24px;
background:
linear-gradient(180deg, rgb(255 255 255 / 0.06), transparent 130px),
linear-gradient(145deg, rgb(15 23 42 / 0.96), rgb(2 6 23 / 0.96));
box-shadow:
0 26px 80px rgb(0 0 0 / 0.45),
0 0 0 1px rgb(255 255 255 / 0.04),
0 0 44px rgb(37 99 235 / 0.2);
}
.ft-window-bar {
position: relative;
z-index: 1;
height: 64px;
display: flex;
align-items: center;
gap: 12px;
padding: 0 32px;
border-bottom: 1px solid rgb(148 163 184 / 0.12);
background: rgb(255 255 255 / 0.025);
}
.ft-window-bar span {
width: 14px;
height: 14px;
border-radius: 999px;
background: #2563eb;
box-shadow: 0 0 20px rgb(37 99 235 / 0.55);
}
.ft-window-bar span:nth-child(2) {
background: #1d4ed8;
}
.ft-window-bar span:nth-child(3) {
background: #334155;
box-shadow: none;
}
.ft-form {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
gap: 20px;
padding: clamp(32px, 6vw, 44px);
text-align: left;
}
.ft-icon {
display: flex;
align-items: center;
justify-content: center;
width: 56px;
height: 56px;
border-radius: 16px;
background: rgb(15 23 42 / 0.5);
border: 1px solid rgb(59 130 246 / 0.45);
box-shadow: 0 0 24px rgb(37 99 235 / 0.18);
}
.ft-icon svg {
width: 24px;
height: 24px;
color: #3b82f6;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.ft-form-heading h2 {
margin: 0;
color: #f8fafc;
font-size: clamp(24px, 5vw, 30px);
line-height: 1.15;
letter-spacing: -0.03em;
}
.ft-form-heading p {
margin: 8px 0 0;
color: #94a3b8;
font-size: clamp(14px, 2.4vw, 15px);
line-height: 1.55;
}
.ft-progress-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.ft-progress-label {
flex: 0 0 auto;
color: #cbd5e1;
font-size: 14px;
font-weight: 500;
}
.ft-step-dots {
display: flex;
align-items: center;
flex: 1 1 auto;
min-width: 0;
}
.ft-step-dot {
flex: 0 0 auto;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 999px;
border: 2px solid rgb(148 163 184 / 0.3);
background: rgb(15 23 42 / 0.5);
color: #94a3b8;
font-size: 13px;
font-weight: 600;
transition:
background-color 200ms ease,
border-color 200ms ease,
color 200ms ease,
box-shadow 200ms ease;
}
.ft-step-dot.is-active {
background: #2563eb;
border-color: #2563eb;
color: #fff;
box-shadow:
0 0 0 4px rgb(37 99 235 / 0.18),
0 0 18px rgb(37 99 235 / 0.5);
}
.ft-step-dot.is-complete {
background: #2563eb;
border-color: #2563eb;
color: #fff;
}
.ft-step-line {
flex: 1 1 auto;
min-width: 8px;
height: 2px;
margin: 0 4px;
background: rgb(148 163 184 / 0.25);
transition: background-color 200ms ease;
}
.ft-step-line.is-complete {
background: #2563eb;
}
.ft-progress-track {
height: 6px;
border-radius: 999px;
background: rgb(148 163 184 / 0.16);
overflow: hidden;
}
.ft-progress-bar {
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, #3b82f6, #2563eb);
transition: width 300ms ease;
}
.ft-sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.ft-step {
border: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 18px;
}
.ft-hidden {
display: none !important;
}
.ft-field {
display: flex;
flex-direction: column;
gap: 8px;
}
.ft-field > label {
color: #e2e8f0;
font-size: 14px;
font-weight: 600;
}
.ft-field > label em {
color: #64748b;
font-style: normal;
font-weight: 500;
}
.ft-control {
position: relative;
display: flex;
align-items: center;
}
.ft-control svg {
position: absolute;
left: 16px;
width: 18px;
height: 18px;
color: #3b82f6;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
pointer-events: none;
}
.ft-control input,
.ft-control select {
width: 100%;
height: 50px;
border: 1px solid rgb(148 163 184 / 0.24);
border-radius: 10px;
background: rgb(15 23 42 / 0.52);
color: #f8fafc;
font: inherit;
font-size: 15px;
padding: 0 16px 0 48px;
outline: none;
appearance: none;
transition:
border-color 160ms ease,
background-color 160ms ease,
box-shadow 160ms ease;
}
.ft-control select {
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='%2394a3b8' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 16px center;
}
.ft-control input::placeholder {
color: #64748b;
}
.ft-control input:hover,
.ft-control select:hover {
border-color: rgb(148 163 184 / 0.38);
}
.ft-control input:focus-visible,
.ft-control select:focus-visible {
border-color: #2563eb;
box-shadow: 0 0 0 4px rgb(37 99 235 / 0.18);
}
.ft-radio-group {
display: flex;
flex-direction: column;
gap: 10px;
}
.ft-radio {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 14px;
border: 1px solid rgb(148 163 184 / 0.24);
border-radius: 10px;
background: rgb(15 23 42 / 0.5);
color: #e2e8f0;
font-size: 14px;
cursor: pointer;
transition:
border-color 160ms ease,
background-color 160ms ease;
}
.ft-radio:hover {
border-color: rgb(148 163 184 / 0.4);
}
.ft-radio:has(input:checked) {
border-color: #2563eb;
background: rgb(37 99 235 / 0.12);
}
.ft-radio input {
width: 16px;
height: 16px;
accent-color: #3b82f6;
cursor: pointer;
}
.ft-field textarea {
width: 100%;
resize: vertical;
min-height: 100px;
border: 1px solid rgb(148 163 184 / 0.24);
border-radius: 10px;
background: rgb(15 23 42 / 0.52);
color: #f8fafc;
font: inherit;
font-size: 15px;
padding: 14px 16px;
outline: none;
transition:
border-color 160ms ease,
background-color 160ms ease,
box-shadow 160ms ease;
}
.ft-field textarea::placeholder {
color: #64748b;
}
.ft-field textarea:hover {
border-color: rgb(148 163 184 / 0.38);
}
.ft-field textarea:focus-visible {
border-color: #2563eb;
box-shadow: 0 0 0 4px rgb(37 99 235 / 0.18);
}
.ft-actions {
display: flex;
justify-content: space-between;
gap: 12px;
}
.ft-btn {
appearance: none;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
height: 50px;
border-radius: 10px;
padding: 0 22px;
font: inherit;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition:
filter 160ms ease,
transform 160ms ease,
box-shadow 160ms ease;
}
.ft-btn svg {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.ft-btn--primary {
border: 0;
flex: 1 1 auto;
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
color: #fff;
box-shadow:
inset 0 1px 0 rgb(255 255 255 / 0.2),
0 16px 34px rgb(37 99 235 / 0.18);
}
.ft-btn--primary:hover {
filter: brightness(1.05);
}
.ft-btn--primary:active {
transform: translateY(1px);
}
.ft-btn--ghost {
border: 1px solid rgb(148 163 184 / 0.24);
background: rgb(15 23 42 / 0.4);
color: #cbd5e1;
}
.ft-btn--ghost:hover:not(:disabled) {
border-color: rgb(148 163 184 / 0.4);
background: rgb(15 23 42 / 0.6);
}
.ft-btn:focus-visible {
outline: none;
box-shadow: 0 0 0 4px rgb(37 99 235 / 0.22);
}
.ft-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.ft-status {
min-height: 20px;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
color: #94a3b8;
font-size: 13px;
}
.ft-status svg {
width: 16px;
height: 16px;
flex: 0 0 auto;
color: #34d399;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
.ft-status[data-state="error"] {
color: #f87171;
}
.ft-status[data-state="error"] svg {
color: #f87171;
}
@media (max-width: 480px) {
.ft-survey-demo {
padding: 18px;
}
.ft-survey-demo::before,
.ft-survey-demo::after {
display: none;
}
.ft-window {
border-radius: 20px;
}
.ft-window-bar {
height: 54px;
padding: 0 22px;
}
.ft-form {
gap: 16px;
padding: 28px 22px;
}
.ft-step-dot {
width: 26px;
height: 26px;
font-size: 12px;
}
.ft-btn {
height: 46px;
padding: 0 16px;
font-size: 14px;
}
}
</style>
<script>
var FT_TOTAL = 4;
var ftCurrent = 1;
function ftUpdateUI(n) {
for (var i = 1; i <= FT_TOTAL; i++) {
var step = document.getElementById("ft-step-" + i);
if (step) step.classList.toggle("ft-hidden", i !== n);
var dot = document.querySelector('[data-dot="' + i + '"]');
if (dot) {
dot.classList.toggle("is-active", i === n);
dot.classList.toggle("is-complete", i < n);
}
var line = document.querySelector('[data-line="' + i + '"]');
if (line) line.classList.toggle("is-complete", i < n);
}
document.getElementById("ft-step-current").textContent = n;
var pct = ((n - 1) / (FT_TOTAL - 1)) * 100;
document.getElementById("ft-bar").style.width = pct + "%";
document
.querySelector(".ft-progress-track")
.setAttribute("aria-valuenow", n);
}
function ftGoTo(n) {
ftCurrent = Math.min(Math.max(n, 1), FT_TOTAL);
ftUpdateUI(ftCurrent);
}
document.querySelectorAll("[data-next]").forEach(function (button) {
button.addEventListener("click", function () {
var current = Number(button.dataset.next);
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) ftGoTo(current + 1);
});
});
document.querySelectorAll("[data-prev]").forEach(function (button) {
button.addEventListener("click", function () {
ftGoTo(Number(button.dataset.prev) - 1);
});
});
</script>
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 endpointPaste 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.