Multi-step forms help users complete complex tasks without facing every field at once. They are common in onboarding flows, checkout processes, account setup, and configuration wizards. However, dividing a form into multiple screens introduces additional challenges around validation, navigation, and preserving user input.
Define the Form Steps
Start by identifying the information required at each stage. Group related fields together so that each step has a clear purpose.
For example, a registration wizard might contain:
- Personal information.
- Contact details.
- Preferences.
- Review and submission.
Avoid splitting a simple form into many screens without a clear usability benefit.
Keep Form Data in a Shared State
A step component may unmount when the user navigates to another step. If its input state exists only inside that component, the values may disappear.
Store the complete form data in a parent component or a dedicated form-management solution.
import { useState } from "react";
function RegistrationWizard() {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState({
name: "",
email: "",
company: "",
});
function updateField(field, value) {
setFormData((current) => ({
...current,
[field]: value,
}));
}
function nextStep() {
setStep((current) => Math.min(current + 1, 2));
}
function previousStep() {
setStep((current) => Math.max(current - 1, 0));
}
return (
<section>
<p>Step {step + 1} of 3</p>
{step === 0 && (
<label>
Name
<input
value={formData.name}
onChange={(event) =>
updateField("name", event.target.value)
}
/>
</label>
)}
{step === 1 && (
<label>
Email
<input
type="email"
value={formData.email}
onChange={(event) =>
updateField("email", event.target.value)
}
/>
</label>
)}
{step === 2 && (
<label>
Company
<input
value={formData.company}
onChange={(event) =>
updateField("company", event.target.value)
}
/>
</label>
)}
<button onClick={previousStep} disabled={step === 0}>
Back
</button>
<button onClick={nextStep} disabled={step === 2}>
Continue
</button>
</section>
);
}
This is a simplified example of state preservation. A production form should add validation, a final submission action, and a review of the collected information.
Validate Before Moving Forward
Each step should validate the fields that matter before allowing the user to continue. Do not wait until the final submission to reveal every error.
Validation should provide specific guidance, such as explaining that an email address is required. Place error messages close to the relevant fields and associate them with their inputs for accessibility.
Client-side validation improves the experience, but the server must validate submitted data independently.
Make Navigation Accessible
Users should understand their current position and what remains. Provide a clear step indicator, meaningful button labels, and a predictable way to return to earlier steps.
When a step changes, consider moving focus to its heading so keyboard and screen-reader users can understand the transition. Avoid relying on color alone to communicate progress.
Handle Interrupted Submissions
A network error during final submission should not erase the user’s completed form. Preserve the input and provide a safe retry mechanism.
If the operation creates important records, consider how duplicate submissions will be prevented. Disable repeated submission while a request is in progress, but also enforce appropriate duplicate protection on the server.
Conclusion
Reliable multi-step forms preserve state, validate incrementally, and make navigation understandable. By treating validation, accessibility, and submission failures as core requirements, developers can create forms that are easier for users to complete and easier to maintain.