Free tool
Multi-step form generator
Long forms convert better in steps. Add step breaks, name each step, and copy a multi-step form with Back / Next buttons, a progress indicator, and validation on every step. No library, no backend, and it still works as one page if JavaScript is off.
Fields
Step 1
Step 2
Step 3
Submission
Where submissions go. Paste your form ID to make the code real — or keep the placeholder while you experiment.
On submit
Spam & privacy
Design
Styling
Adds Tailwind utility classes. Your project needs Tailwind installed.
Live preview
Step 1 / 3
The preview can't run scripts, so use the step arrows above. In your copied code, Back and Next work and each step validates before moving on.
contact-form.html
<form class="flex w-full max-w-md flex-col gap-4" action="https://shipmyform.com/f/YOUR_FORM_ID" method="POST" id="contact-form">
<p class="text-xs text-neutral-400" data-smf-progress aria-live="polite" hidden>Step 1 of 3</p>
<div class="space-y-4" data-smf-step>
<h3 class="text-base font-semibold text-neutral-200">About you</h3>
<label class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-neutral-200">Name <span class="text-red-500">*</span></span>
<input class="w-full rounded-lg border px-3 py-2 text-sm outline-none border-neutral-700 bg-neutral-900 text-neutral-100 placeholder-neutral-500 focus:border-[#ffffff] focus:ring-2 focus:ring-[#ffffff]/30" type="text" name="name" placeholder="Ada Lovelace" required />
</label>
<label class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-neutral-200">Email <span class="text-red-500">*</span></span>
<input class="w-full rounded-lg border px-3 py-2 text-sm outline-none border-neutral-700 bg-neutral-900 text-neutral-100 placeholder-neutral-500 focus:border-[#ffffff] focus:ring-2 focus:ring-[#ffffff]/30" type="email" name="email" placeholder="[email protected]" required />
</label>
</div>
<div class="space-y-4" data-smf-step>
<h3 class="text-base font-semibold text-neutral-200">Your project</h3>
<label class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-neutral-200">What do you need? <span class="text-red-500">*</span></span>
<select class="w-full rounded-lg border px-3 py-2 text-sm outline-none border-neutral-700 bg-neutral-900 text-neutral-100 placeholder-neutral-500 focus:border-[#ffffff] focus:ring-2 focus:ring-[#ffffff]/30" name="project_type" required>
<option value="" disabled selected>Choose…</option>
<option value="New website">New website</option>
<option value="Redesign">Redesign</option>
<option value="Web app">Web app</option>
<option value="Something else">Something else</option>
</select>
</label>
<fieldset class="flex flex-col gap-2 border-0 p-0">
<legend class="mb-1 text-sm font-medium text-neutral-200">Budget</legend>
<label class="flex items-center gap-2 text-sm text-neutral-200">
<input type="radio" name="budget" value="Under $5k" />
<span>Under $5k</span>
</label>
<label class="flex items-center gap-2 text-sm text-neutral-200">
<input type="radio" name="budget" value="$5k to $20k" />
<span>$5k to $20k</span>
</label>
<label class="flex items-center gap-2 text-sm text-neutral-200">
<input type="radio" name="budget" value="Over $20k" />
<span>Over $20k</span>
</label>
</fieldset>
</div>
<div class="space-y-4" data-smf-step>
<h3 class="text-base font-semibold text-neutral-200">Details</h3>
<label class="flex flex-col gap-1.5">
<span class="text-sm font-medium text-neutral-200">Tell us more <span class="text-red-500">*</span></span>
<textarea class="w-full rounded-lg border px-3 py-2 text-sm outline-none border-neutral-700 bg-neutral-900 text-neutral-100 placeholder-neutral-500 focus:border-[#ffffff] focus:ring-2 focus:ring-[#ffffff]/30 min-h-[120px]" name="message" placeholder="Goals, timeline, links…" required></textarea>
</label>
</div>
<input type="hidden" name="_subject" value="New project request" />
<!-- Anti-spam honeypot, leave this empty -->
<input type="text" name="_gotcha" tabindex="-1" autocomplete="off" aria-hidden="true" style="position:absolute;left:-9999px" />
<div class="flex gap-2">
<button class="rounded-lg cursor-pointer border px-4 py-2.5 text-sm font-semibold border-neutral-700 text-neutral-200" type="button" data-smf-back hidden>Back</button>
<button class="rounded-lg cursor-pointer bg-[#ffffff] px-4 py-2.5 text-sm font-semibold text-black transition-opacity hover:opacity-90" type="button" data-smf-next hidden>Next</button>
<button class="rounded-lg cursor-pointer bg-[#ffffff] px-4 py-2.5 text-sm font-semibold text-black transition-opacity hover:opacity-90" type="submit" data-smf-submit>Send request</button>
</div>
</form>
<script>
(() => {
const form = document.getElementById("contact-form");
const steps = [...form.querySelectorAll("[data-smf-step]")];
const back = form.querySelector("[data-smf-back]");
const next = form.querySelector("[data-smf-next]");
const submit = form.querySelector("[data-smf-submit]");
const progress = form.querySelector("[data-smf-progress]");
const last = steps.length - 1;
let current = 0;
function show(i) {
current = i;
steps.forEach((el, n) => (el.hidden = n !== i));
back.hidden = i === 0;
next.hidden = i === last;
submit.hidden = i !== last;
progress.hidden = false;
progress.textContent = "Step " + (i + 1) + " of " + steps.length;
}
// Native validation checks the whole form, including required fields on
// steps the visitor can't see yet, which would silently block Enter. So turn
// it off and validate one visible step at a time instead.
form.noValidate = true;
// Stops at (and reports) the first problem on the current step.
function valid() {
return [...steps[current].querySelectorAll("input, select, textarea")].every(
(el) => el.reportValidity(),
);
}
next.addEventListener("click", () => valid() && show(current + 1));
back.addEventListener("click", () => show(current - 1));
form.addEventListener("submit", (e) => {
// Let the real submit through only from a valid last step. Pressing Enter
// on an earlier step moves forward instead of submitting.
if (current === last && valid()) return;
e.preventDefault();
e.stopImmediatePropagation();
if (current < last && valid()) show(current + 1);
});
show(0);
})();
</script>How it works
- Choose a template and fields. Pick a template, then add, remove, or reorder fields and mark the required ones. Add step breaks to turn a long form into a multi-step form.
- Style it. Pick CSS, Tailwind, or unstyled output and tune the theme: accent color, light or dark, rounded corners.
- Copy your framework's code. Switch to HTML, React, Vue, Svelte, Astro, or a fetch() version and copy, download, or open it in CodePen.
- Connect an endpoint. Create a free ShipMyForm form to get a real endpoint, then drop your form ID in so submissions actually arrive.
Want the background? Read what a form backend is and how to stop form spam without reCAPTCHA. Already have a Google Form? Convert it to HTML.
Frequently asked questions
- How does the multi-step form work?
- It is one normal HTML form split into step sections. A short script shows one step at a time, checks the visible step's required fields before Next, and reveals Submit on the last step. Everything is submitted together in a single request, so your backend sees one ordinary submission.
- Does it need a library like a form wizard plugin?
- No. The HTML and Astro output use about 30 lines of dependency-free JavaScript. The React, Vue, and Svelte output use each framework's own state, with no extra packages.
- What happens if JavaScript is disabled?
- In the HTML and Astro output every step is visible and the Back / Next buttons stay hidden, so the form degrades to a regular single-page form with one Submit button. Nothing is lost.
- Can each step validate before moving on?
- Yes. Clicking Next runs the browser's built-in validation on the current step only and stops at the first problem. Pressing Enter on an early step moves forward instead of submitting the whole form.
- Is this multi-step form generator free?
- Yes. Generating and copying the code is completely free and needs no signup. You only create a free ShipMyForm account when you want the form to actually receive submissions.
- Does the multi-step form need a backend?
- No. The form posts to a hosted endpoint, so it works on any static site. Submissions are stored, spam-filtered, and emailed to you without a server.