Building a Tab Form Component: A Complete Guide
Multi-step form with tab navigation, validation, and progress. Here is the complete implementation.
Building a Tab Form Component: A Complete Guide
A tab form (multi-step form) breaks a long form into smaller steps. Here is the complete implementation.
Requirements
- Multiple tabs/steps (e.g., Personal, Address, Payment).
- Navigate between tabs.
- Validate each step before proceeding.
- Progress indicator.
- Final submit.
- Data persists across tabs.
HTML
<div class="tab-form"> <div class="tabs" id="tabs"> <button class="tab active" data-step="0">1. Personal</button> <button class="tab" data-step="1">2. Address</button> <button class="tab" data-step="2">3. Payment</button> </div> <div class="tab-content" id="tabContent"></div> <div class="actions"> <button id="prevBtn" onclick="prevStep()">Prev</button> <button id="nextBtn" onclick="nextStep()">Next</button> <button id="submitBtn" onclick="submitForm()" style="display:none">Submit</button> </div> </div>
JavaScript
let currentStep = 0; const formData = { name: "", email: "", address: "", city: "", card: "", expiry: "" }; const steps = [ { title: "Personal Info", fields: [ { name: "name", label: "Name", type: "text", required: true }, { name: "email", label: "Email", type: "email", required: true }, ], }, { title: "Address", fields: [ { name: "address", label: "Address", type: "text", required: true }, { name: "city", label: "City", type: "text", required: true }, ], }, { title: "Payment", fields: [ { name: "card", label: "Card Number", type: "text", required: true }, { name: "expiry", label: "Expiry", type: "text", required: true }, ], }, ]; function renderStep() { const step = steps[currentStep]; const html = step.fields.map((field) => ` <div class="form-field"> <label>${field.label}</label> <input type="${field.type}" name="${field.name}" value="${formData[field.name] || ""}" onchange="updateField('${field.name}', this.value)" /> </div> `).join(""); document.getElementById("tabContent").innerHTML = html; document.querySelectorAll(".tab").forEach((tab, i) => { tab.classList.toggle("active", i === currentStep); }); document.getElementById("prevBtn").style.display = currentStep === 0 ? "none" : "inline"; document.getElementById("nextBtn").style.display = currentStep === steps.length - 1 ? "none" : "inline"; document.getElementById("submitBtn").style.display = currentStep === steps.length - 1 ? "inline" : "none"; } function updateField(name, value) { formData[name] = value; } function validateStep() { const step = steps[currentStep]; return step.fields.every((field) => { if (field.required && !formData[field.name]) { alert(`${field.label} is required`); return false; } return true; }); } function nextStep() { if (!validateStep()) return; if (currentStep < steps.length - 1) { currentStep++; renderStep(); } } function prevStep() { if (currentStep > 0) { currentStep--; renderStep(); } } function submitForm() { if (!validateStep()) return; console.log("Form submitted:", formData); alert("Form submitted successfully!"); } document.querySelectorAll(".tab").forEach((tab) => { tab.addEventListener("click", () => { const step = parseInt(tab.dataset.step); if (step > currentStep && !validateStep()) return; currentStep = step; renderStep(); }); }); renderStep();
CSS
.tabs { display: flex; gap: 4px; } .tab { padding: 10px 20px; border: 1px solid #ccc; background: #f0f0f0; cursor: pointer; } .tab.active { background: #007bff; color: white; border-color: #007bff; } .tab-content { padding: 20px; border: 1px solid #ccc; border-top: none; min-height: 200px; } .form-field { margin-bottom: 12px; } .form-field label { display: block; margin-bottom: 4px; font-weight: bold; } .form-field input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; } .actions { margin-top: 16px; display: flex; gap: 8px; }
The Takeaway
Tab form: steps array (each with fields), formData object (persists across tabs), renderStep (renders the current step's fields), validateStep (checks required fields), nextStep/prevStep (navigate with validation), submitForm (final submit). Tab clicks validate before jumping forward. Data persists in the formData object.
Define steps as an array of field configs. Store all form data in a single object (persists across tabs). Render the current step's fields. Validate before proceeding to the next step. Show prev/next buttons and a submit button on the last step.
Before allowing the user to proceed, check all required fields in the current step: steps[currentStep].fields.every(field => !field.required || formData[field.name]). If validation fails, show an error and prevent navigation.
Store all data in a single object: const formData = { name: '', email: '', address: '' }. Update on every input change: formData[field.name] = value. The data persists because the object is in the outer scope, not re-created on each step.
Track currentStep. Next button: validate, increment currentStep, re-render. Prev button: decrement currentStep, re-render. Tab click: if jumping forward, validate current step first. Disable or prevent jumping to unvalidated steps.
Check if currentStep === steps.length - 1. If so, show the submit button and hide the next button. Otherwise, show the next button and hide the submit button. Update this in the renderStep function.
Ready to master React completely?
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course to dive deeper with high-quality video tutorials, solve interview questions, and a premium community.
Master React
Want to upskill yourself, crack your next interview, and get your dream job? Join our comprehensive course.

