3 Pure CSS4 with JSPublished 2026-10-05Free · no sign-up
Seven patterns for breaking a long form into steps: a three-step sign-up with validation, a numbered checkout header, a first step of large choice cards, a review step with edit buttons, one question per screen, a side step list that stacks on phones and a step that saves progress as you type.
A sign-up split into three fieldsets, one shown at a time, with a Step 2 of 3 label and a progress bar. Next checks the current step's fields with reportValidity before moving on, and focus moves to the new step's legend so screen reader users hear where they are.
var f = document.querySelector('.msf1'), sets = f.querySelectorAll('fieldset'), i = 0;
var next = f.querySelector('.next'), back = f.querySelector('.back');
function show(n) {
sets[i].hidden = true; i = n; sets[i].hidden = false;
f.querySelector('.cnt').textContent = 'Step ' + (i + 1) + ' of ' + sets.length;
f.querySelector('.bar span').style.width = (i + 1) / sets.length * 100 + '%';
back.hidden = i === 0; next.textContent = i === sets.length - 1 ? 'Create account' : 'Next';
sets[i].querySelector('legend').focus();
}
next.onclick = function () {
var ok = Array.prototype.every.call(sets[i].querySelectorAll('input'), function (x) { return x.reportValidity(); });
if (!ok) return;
if (i < sets.length - 1) show(i + 1); else f.querySelector('.cnt').textContent = 'Account created (demo)';
};
back.onclick = function () { show(i - 1); };
02. Numbered Steps Header
FormStepper
The top of a checkout form: numbered circles for Cart, Address and Payment joined by lines that fill as steps complete. The list is ordered, the current step has aria-current set to step, and completed steps carry the word done for screen readers as well as a tick.
A first step that asks one question with large selectable cards. Each card is a label wrapping a real radio button, so arrow keys move between them, and :has(:checked) styles the chosen card. The Continue button stays disabled until something is picked.
View code
<form class="msf3"><fieldset><legend>What are you building?</legend><label><input type="radio" name="k" value="shop"><b>An online shop</b><span>Products, cart and checkout</span></label><label><input type="radio" name="k" value="site"><b>A company site</b><span>Pages, blog and contact form</span></label><label><input type="radio" name="k" value="app"><b>A web app</b><span>Accounts and dashboards</span></label></fieldset><button disabled>Continue</button></form>
var f = document.querySelector('.msf3');
f.addEventListener('change', function () { f.querySelector('button').disabled = false; });
f.addEventListener('submit', function (e) { e.preventDefault(); });
04. Review Step with Edit Links
FormSummary
The last step before submitting: a description list of everything entered, each with an Edit button that names what it edits (Edit email, not just Edit). Showing a summary lets people catch mistakes before they commit, which matters most for orders and bookings.
An onboarding flow that asks a single question at a time, sliding the next one in from the right. Enter moves forward, the question is the label of its input so it is announced when focused, and a small counter shows how far along the person is.
View code
<form class="msf5"><p class="c">1 / 3</p><div class="q on"><label for="q1">What should we call you?</label><input id="q1" autocomplete="given-name" placeholder="Your first name"></div><div class="q"><label for="q2">What do you sell?</label><input id="q2" placeholder="Candles, prints, coffee…"></div><div class="q"><label for="q3">Where are you based?</label><input id="q3" autocomplete="country-name" placeholder="Country"></div><button>Next →</button></form>
var f = document.querySelector('.msf5'), qs = f.querySelectorAll('.q'), i = 0;
f.addEventListener('submit', function (e) {
e.preventDefault();
if (i === qs.length - 1) { f.querySelector('.c').textContent = 'All done'; return; }
qs[i].classList.remove('on'); i++; qs[i].classList.add('on');
f.querySelector('.c').textContent = (i + 1) + ' / ' + qs.length;
qs[i].querySelector('input').focus();
});
06. Side Step List Layout
FormLayout
A wider layout with the step list beside the form, which stacks above it on narrow screens through a container query. The list is navigation between sections, so each completed step is a link back, the current one is marked, and future steps are plain text.
Long forms lose people when a tab closes. This step saves each field to sessionStorage as it is typed and restores it on load, with a quiet Saved note in a live region. Clearing the form removes the stored copy, and nothing ever leaves the browser.
View code
<form class="msf7"><h3>Step 2 of 4 · About you</h3><label for="m7b">Short bio</label><textarea id="m7b" rows="4" name="bio"></textarea><div class="row"><p class="s" aria-live="polite"></p><button type="reset">Clear</button></div></form>
var f = document.querySelector('.msf7'), t = f.querySelector('textarea'), s = f.querySelector('.s'), key = 'msf7-bio', tm;
try { t.value = sessionStorage.getItem(key) || ''; } catch (e) {}
t.addEventListener('input', function () {
clearTimeout(tm);
tm = setTimeout(function () { try { sessionStorage.setItem(key, t.value); s.textContent = 'Saved'; } catch (e) { s.textContent = 'Could not save in this browser'; } }, 400);
});
f.addEventListener('reset', function () { try { sessionStorage.removeItem(key); } catch (e) {} s.textContent = 'Cleared'; });
About these CSS Multi-Step Forms
Say where people are
Show Step 2 of 3 in text, not only as dots or a bar, and mark the current step with aria-current="step". When the step changes, move focus to its heading or legend so screen reader users hear the new step.
Validate each step
Check the fields of the current step before moving on. reportValidity() on each input shows the browser's own messages and puts focus on the first problem, with no extra validation library.
Group steps with fieldsets
Each step is a <fieldset> with a <legend>, so the step's name is announced with its fields. Hide the steps that are not active with the hidden attribute so they cannot be tabbed into.
Let people go back and review
A Back button and a final review step catch mistakes before submission. Keep what was typed when moving between steps, and consider saving it so a closed tab does not lose the work.
Frequently asked questions
Is a multi-step form better than one long form?
For long or branching forms, usually yes: each step feels short. For three or four fields, one page is faster.
Does the saved-progress demo send my data anywhere?
No. It uses sessionStorage, which stays in your browser and is cleared when the tab closes.
Can I submit all steps as one form?
Yes. Keep every fieldset inside one form element; hidden fieldsets still submit their values.