Bootstrap 5 Multi-Step Form
A multi-step form breaks a long or complex form into sequential sections, showing one step at a time with progress indicators between them. Bootstrap 5 does not ship a dedicated multi-step form component, but you can build one using the tab system, nav pills, cards, and utility classes combined with minimal JavaScript to control step visibility. Use this pattern whenever a form has more than five fields, collects sensitive data across logical groups (contact details, then payment, then confirmation), or when research shows users abandon single-page forms at high rates.
Bootstrap class
No dedicated Bootstrap class
Common use cases
- SaaS onboarding flow that collects account details on step 1, billing information on step 2, and team member invitations on step 3
- Insurance quote calculator that walks users through vehicle details, driver history, and coverage preferences before showing a price
- Event registration form that separates ticket selection, attendee details, and dietary or accessibility requirements into distinct steps
- Job application form that groups resume upload and work history on step 1, references on step 2, and equal opportunities data on step 3
Variants and classes
| Variant | Description and classes |
|---|---|
| Step header | Numbered steps across the top.d-flex justify-content-between align-items-center |
| Step panels | One panel visible at a time.card + d-none on inactive panels |
| Progress bar | Shows how far through the form the user is.progress + progress-bar |
| Navigation buttons | Back and next buttons at the bottom.d-flex justify-content-between + btn btn-outline-secondary / btn btn-primary |
Code example
<div class="container py-5">
<div class="row justify-content-center">
<div class="col-lg-7">
<!-- Step progress indicator -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="step-item text-center">
<div class="step-circle bg-primary text-white rounded-circle d-inline-flex align-items-center justify-content-center" style="width:40px;height:40px;" id="circle-1">1</div>
<div class="small mt-1 fw-semibold">Account</div>
</div>
<div class="flex-grow-1 border-top border-2 mx-2"></div>
<div class="step-item text-center">
<div class="step-circle bg-secondary text-white rounded-circle d-inline-flex align-items-center justify-content-center" style="width:40px;height:40px;" id="circle-2">2</div>
<div class="small mt-1 text-muted">Billing</div>
</div>
<div class="flex-grow-1 border-top border-2 mx-2"></div>
<div class="step-item text-center">
<div class="step-circle bg-secondary text-white rounded-circle d-inline-flex align-items-center justify-content-center" style="width:40px;height:40px;" id="circle-3">3</div>
<div class="small mt-1 text-muted">Confirm</div>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-4">
<!-- Step 1: Account Details -->
<div class="step-panel" id="step-1">
<h5 class="card-title mb-4">Create your account</h5>
<div class="mb-3">
<label for="fullName" class="form-label">Full name</label>
<input type="text" class="form-control" id="fullName" placeholder="Jane Smith">
</div>
<div class="mb-3">
<label for="email" class="form-label">Email address</label>
<input type="email" class="form-control" id="email" placeholder="jane@example.com">
</div>
<div class="mb-4">
<label for="password" class="form-label">Password</label>
<input type="password" class="form-control" id="password" placeholder="Min. 8 characters">
</div>
<div class="d-flex justify-content-end">
<button class="btn btn-primary" onclick="goToStep(2)">Next: Billing <i class="bi bi-arrow-right ms-1"></i></button>
</div>
</div>
<!-- Step 2: Billing -->
<div class="step-panel d-none" id="step-2">
<h5 class="card-title mb-4">Billing information</h5>
<div class="mb-3">
<label for="cardName" class="form-label">Name on card</label>
<input type="text" class="form-control" id="cardName" placeholder="Jane Smith">
</div>
<div class="mb-3">
<label for="cardNumber" class="form-label">Card number</label>
<input type="text" class="form-control" id="cardNumber" placeholder="1234 5678 9012 3456">
</div>
<div class="row g-3 mb-4">
<div class="col-6">
<label for="expiry" class="form-label">Expiry date</label>
<input type="text" class="form-control" id="expiry" placeholder="MM / YY">
</div>
<div class="col-6">
<label for="cvv" class="form-label">CVV</label>
<input type="text" class="form-control" id="cvv" placeholder="123">
</div>
</div>
<div class="d-flex justify-content-between">
<button class="btn btn-outline-secondary" onclick="goToStep(1)"><i class="bi bi-arrow-left me-1"></i> Back</button>
<button class="btn btn-primary" onclick="goToStep(3)">Next: Confirm <i class="bi bi-arrow-right ms-1"></i></button>
</div>
</div>
<!-- Step 3: Confirm -->
<div class="step-panel d-none" id="step-3">
<h5 class="card-title mb-4">Review and confirm</h5>
<div class="alert alert-light border mb-4">
<p class="mb-1"><strong>Plan:</strong> Pro Monthly at $29/month</p>
<p class="mb-1"><strong>Billing:</strong> Card ending in 3456</p>
<p class="mb-0"><strong>Renews:</strong> Automatically each month. Cancel anytime.</p>
</div>
<div class="form-check mb-4">
<input class="form-check-input" type="checkbox" id="termsCheck">
<label class="form-check-label" for="termsCheck">
I agree to the <a href="#">Terms of Service</a> and <a href="#">Privacy Policy</a>
</label>
</div>
<div class="d-flex justify-content-between">
<button class="btn btn-outline-secondary" onclick="goToStep(2)"><i class="bi bi-arrow-left me-1"></i> Back</button>
<button class="btn btn-success" type="submit">Confirm and Subscribe</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function goToStep(step) {
document.querySelectorAll('.step-panel').forEach(function(panel) {
panel.classList.add('d-none');
});
document.getElementById('step-' + step).classList.remove('d-none');
[1, 2, 3].forEach(function(n) {
var circle = document.getElementById('circle-' + n);
if (n < step) {
circle.classList.remove('bg-secondary');
circle.classList.add('bg-success');
} else if (n === step) {
circle.classList.remove('bg-secondary', 'bg-success');
circle.classList.add('bg-primary');
} else {
circle.classList.remove('bg-primary', 'bg-success');
circle.classList.add('bg-secondary');
}
});
}
</script>Live examples
Basic Multi-Step Form
Canvas template variants
Canvas Builder builds pages on the Canvas HTML template, which adds its own styled versions of Bootstrap 5 components. Variants you can ask for in a prompt:
- Canvas Builder generated multi-step form with custom colours
- Multi-Step Form with interactive states
- Responsive multi-step form for all screen sizes
Best practices
Validate each step before advancing
Run Bootstrap 5 native validation (using the was-validated class on the form and the required attribute on inputs) inside your goToStep function before showing the next panel, so users cannot skip required fields and encounter a wall of errors at the final step.
Preserve field values when navigating backwards
Because the panels use d-none instead of being removed from the DOM, field values persist automatically when users click Back, which avoids one of the most frustrating multi-step form experiences without any extra state management.
Mark completed steps visually
Swap the step circle colour to bg-success once a step is passed (as shown in the goToStep function above) so users can see their progress at a glance and feel confident they are moving forward through the form.
Keep each step under five fields
If any single step requires more than five fields, consider whether you can split it further or use a collapsible accordion within that step; cognitive overload per screen undermines the entire point of breaking the form apart.
FAQ
How do I handle browser Back button navigation inside a multi-step form?
history.pushState({step: 2}, '', '#step-2') each time the user advances, then listen for the popstate event to call goToStep(event.state.step). This gives users the expected Back behaviour while keeping all field values intact because the DOM is never destroyed.Can I use Bootstrap 5 tabs or nav-pills instead of manually toggling d-none?
nav-tabs or nav-pills with data-bs-toggle='tab') handles panel switching for you and includes built-in ARIA attributes for accessibility. The downside is that the tab links are visible to users and they can skip steps freely. If you need enforced sequential navigation you should disable or hide the tab triggers and drive tab changes programmatically using bootstrap.Tab.getOrCreateInstance(el).show(), which keeps the Bootstrap JavaScript managing panel visibility while your code controls when a user is allowed to advance.