Build a multi-step form with progress indicators, field validation at each step, and a single submission at the end. Improve completion rates for long forms.
TL;DR
To create a multi-step form, split your form into sections shown one at a time using JavaScript. Collect all field values and submit them in a single POST to a FormsList endpoint on the final step. FormsList receives all fields as one submission regardless of how many steps your form has.
Wrap each group of fields in a container (a div with a data-step attribute). Only one step is visible at a time. This approach keeps all fields in a single form element so the data is submitted together at the end.
<form id="multiStepForm" action="https://formslist.com/f/YOUR_FORM_HASH" method="POST">
<!-- Step 1: Personal Info -->
<div class="form-step" data-step="1">
<h3>Step 1: Personal Information</h3>
<label for="name">Full Name</label>
<input type="text" id="name" name="name" required />
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
</div>
<!-- Step 2: Details -->
<div class="form-step" data-step="2" style="display:none;">
<h3>Step 2: Project Details</h3>
<label for="company">Company</label>
<input type="text" id="company" name="company" />
<label for="budget">Budget Range</label>
<select id="budget" name="budget" required>
<option value="">Select...</option>
<option value="<5k">Under $5,000</option>
<option value="5k-15k">$5,000 - $15,000</option>
<option value="15k-50k">$15,000 - $50,000</option>
<option value="50k+">$50,000+</option>
</select>
</div>
<!-- Step 3: Message -->
<div class="form-step" data-step="3" style="display:none;">
<h3>Step 3: Your Message</h3>
<label for="message">Tell us about your project</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<!-- Progress & Navigation -->
<div class="form-nav">
<div class="progress">Step <span id="currentStep">1</span> of 3</div>
<button type="button" id="prevBtn" style="display:none;">Back</button>
<button type="button" id="nextBtn">Next</button>
<button type="submit" id="submitBtn" style="display:none;">Submit</button>
</div>
</form>Write JavaScript to handle moving between steps. The Next button validates the current step's fields before advancing. The Back button returns to the previous step without losing data. On the final step, the Next button is replaced with a Submit button.
<script>
const form = document.getElementById("multiStepForm");
const steps = form.querySelectorAll(".form-step");
const prevBtn = document.getElementById("prevBtn");
const nextBtn = document.getElementById("nextBtn");
const submitBtn = document.getElementById("submitBtn");
const currentStepEl = document.getElementById("currentStep");
let current = 0;
function showStep(index) {
steps.forEach((step, i) => {
step.style.display = i === index ? "block" : "none";
});
currentStepEl.textContent = index + 1;
prevBtn.style.display = index === 0 ? "none" : "inline-block";
nextBtn.style.display = index === steps.length - 1 ? "none" : "inline-block";
submitBtn.style.display = index === steps.length - 1 ? "inline-block" : "none";
}
function validateStep(index) {
const inputs = steps[index].querySelectorAll("input, select, textarea");
for (const input of inputs) {
if (!input.checkValidity()) {
input.reportValidity();
return false;
}
}
return true;
}
nextBtn.addEventListener("click", () => {
if (validateStep(current)) {
current++;
showStep(current);
}
});
prevBtn.addEventListener("click", () => {
current--;
showStep(current);
});
showStep(0);
</script>Add CSS to style the form steps, navigation buttons, and a progress bar. Visual progress indicators help users understand how many steps remain and increase form completion rates. Keep the design clean and consistent across all steps.
<style>
#multiStepForm {
max-width: 500px;
margin: 2rem auto;
font-family: sans-serif;
}
.form-step h3 {
margin-bottom: 1rem;
color: #1e293b;
}
label {
display: block;
margin-top: 0.75rem;
font-weight: 600;
font-size: 0.9rem;
}
input, select, textarea {
width: 100%;
padding: 0.5rem;
margin-top: 0.25rem;
border: 1px solid #d1d5db;
border-radius: 6px;
box-sizing: border-box;
}
.form-nav {
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 1.5rem;
}
.progress {
flex: 1;
font-size: 0.85rem;
color: #6b7280;
}
button {
padding: 0.5rem 1.25rem;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
}
#prevBtn { background: #e5e7eb; color: #374151; }
#nextBtn, #submitBtn { background: #2563eb; color: white; }
#nextBtn:hover, #submitBtn:hover { background: #1d4ed8; }
</style>The form submits all fields from every step in a single POST request when the user clicks Submit on the final step. You can switch to an AJAX submission to stay on the page and show a success message instead of redirecting. FormsList receives all the data regardless of which step each field was on.
<script>
form.addEventListener("submit", async (e) => {
e.preventDefault();
if (!validateStep(current)) return;
submitBtn.disabled = true;
submitBtn.textContent = "Submitting...";
try {
const res = await fetch("https://formslist.com/f/YOUR_FORM_HASH", {
method: "POST",
body: new FormData(form),
});
if (res.ok) {
form.innerHTML = `
<div style="text-align:center;padding:2rem;">
<h3 style="color:#16a34a;">Thank you!</h3>
<p>Your submission has been received. We will be in touch soon.</p>
</div>`;
} else {
alert("Something went wrong. Please try again.");
submitBtn.disabled = false;
submitBtn.textContent = "Submit";
}
} catch {
alert("Network error. Please try again.");
submitBtn.disabled = false;
submitBtn.textContent = "Submit";
}
});
</script>Wrap each group of fields in a div with a data-step attribute. Use JavaScript to show one step at a time, validate before advancing, and submit all data together on the final step.
Before advancing to the next step, call checkValidity() on each input in the current step. Only advance if all fields pass validation. This prevents users from skipping required fields.
Keep all fields inside a single <form> element. When the user clicks Submit on the final step, the browser sends all fields from every step in one POST request. No need to merge data manually.
200 free submissions a month and a live endpoint in 30 seconds. No server, no credit card, just point your form at it.