Form Nhiều Bước
Giao diện người dùng chuyển đổi giữa nhiều bước (có chỉ báo tiến trình), thường được sử dụng trên màn hình đăng ký, v.v.
Đây là thành phần UI dạng nhiều bước (định dạng trình hướng dẫn) được sử dụng trên màn hình có nhiều mục đầu vào, chẳng hạn như đăng ký người dùng và bảng câu hỏi. Nó có chỉ báo tiến trình truyền tải trực quan tiến trình của bạn ở mỗi bước và hình ảnh động trượt mượt mà.
Xem trước
Account Information
Please enter the information used for login.
Personal Information
Please tell us your basic information.
Confirm your input
You will be registered with the following information. Is this okay?
Trường hợp sử dụng & Tính năng
- Đăng ký người dùng/tiếp nhận: Bằng cách yêu cầu người dùng nhập thông tin vào các phần riêng biệt, chúng tôi giảm bớt rào cản tâm lý cho người dùng và ngăn họ rời khỏi trang web giữa chừng (bỏ giỏ hàng hoặc bỏ biểu mẫu).
- Tổ chức đầu vào phức tạp: Giảm tải nhận thức của người dùng bằng cách chia các nhiệm vụ có nhiều mục thành các nhóm logic, chẳng hạn như nhập thông tin địa chỉ giao hàng hoặc bảng câu hỏi.
- Hiển thị tiến trình: Chỉ báo tiến trình ở đầu màn hình cho phép người dùng biết nhanh họ đang ở đâu và mất bao lâu để hoàn thành, giúp người dùng yên tâm.
Những điểm chính của thiết kế và UX (Design & UX)
- Hoạt hình trượt mượt mà: Khi chuyển đổi các bước, nội dung trước và sau sẽ trượt đồng thời mờ dần từ bên này sang bên kia, tạo ra sự liên tục về không gian.
- Xóa phản hồi trạng thái: Các bước đã hoàn thành được đánh dấu bằng dấu kiểm và các bước hiện tại được đánh dấu bằng tiện ích
ringđể truyền đạt trạng thái hiện tại một cách trực quan. - Thiết kế nút khuyến khích hành động: Khi đến bước cuối cùng, màu của nút
Next'' button changes to green (`ngọc lục bảo`), which means completion, and at the same time the label changes toGửi”, khuyến khích rõ ràng hành động cuối cùng.
Cách tùy chỉnh (Hướng dẫn tùy chỉnh)
- Khả năng tương thích với màu sắc của thương hiệu: Bạn có thể dễ dàng tùy chỉnh tông màu bằng cách thay thế các thông số màu như
indigo-600trong mã bằng màu chủ đề của dự án của bạn (ví dụ:blue-600hoặcrose-500). - Thay đổi số bước: Bạn có thể thay đổi linh hoạt số bước bằng cách tăng hoặc giảm bộ
<div class="msf-step-indicator">...</div>và<div class="msf-step-content">...</div>trong HTML và bằng cách kết hợp chúng vớiconst steps = [1, 2, 3];trong JavaScript. - Tích hợp xác thực: Bằng cách triển khai kiểm tra đầu vào (các mục bắt buộc, kiểm tra định dạng, v.v.) cho mỗi bước hiện tại trong sự kiện nhấp vào nút “Tiếp theo” (
btnNext.addEventListener) và thêm quyền kiểm soát để thực thicurrentStep++nếu không có lỗi, nó có thể được sử dụng trong hoạt động thực tế.
Mã triển khai (Implementation)
<div class="multi-step-form-wrapper bg-gray-50 dark:bg-zinc-950 flex items-center justify-center p-6 min-h-[600px] font-sans">
<div class="bg-white dark:bg-zinc-900 rounded-2xl shadow-xl w-full max-w-lg p-8 relative overflow-hidden border border-gray-100 dark:border-zinc-800">
<!-- Progress Bar -->
<div class="flex justify-between items-center mb-10 relative">
<div class="absolute left-0 top-1/2 transform -translate-y-1/2 w-full h-1.5 bg-gray-200 dark:bg-zinc-800 z-0 rounded-full"></div>
<div id="msf-progress-bar" class="absolute left-0 top-1/2 transform -translate-y-1/2 h-1.5 bg-indigo-600 z-0 transition-all duration-500 ease-out rounded-full" style="width: 0%;"></div>
<!-- Step 1 -->
<div class="relative z-10 flex flex-col items-center">
<div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900 ring-4 ring-indigo-100" data-step="1">1</div>
<span class="msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase">Account</span>
</div>
<!-- Step 2 -->
<div class="relative z-10 flex flex-col items-center">
<div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900" data-step="2">2</div>
<span class="msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase">Personal</span>
</div>
<!-- Step 3 -->
<div class="relative z-10 flex flex-col items-center">
<div class="msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900" data-step="3">3</div>
<span class="msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase">Review</span>
</div>
</div>
<!-- Form Steps -->
<div class="relative h-[280px] overflow-visible">
<!-- Step 1 Content -->
<div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-0 opacity-100" data-step="1">
<h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">Account Information</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">Please enter the information used for login.</p>
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Email Address</label>
<input type="email" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="you@example.com" />
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Password</label>
<input type="password" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="••••••••" />
</div>
</div>
</div>
<!-- Step 2 Content -->
<div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none" data-step="2">
<h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">Personal Information</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">Please tell us your basic information.</p>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Last Name</label>
<input type="text" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="Doe" />
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">First Name</label>
<input type="text" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="John" />
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 dark:text-gray-300 mb-1.5">Phone Number</label>
<input type="tel" required class="w-full text-gray-900 dark:text-white placeholder-gray-400 px-4 py-2.5 border border-gray-300 dark:border-zinc-700 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition-all shadow-sm bg-gray-50 dark:bg-zinc-950 focus:bg-white dark:bg-zinc-900" placeholder="090-1234-5678" />
</div>
</div>
</div>
<!-- Step 3 Content -->
<div class="msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none" data-step="3">
<h2 class="text-2xl font-extrabold text-gray-900 dark:text-white mb-2">Confirm your input</h2>
<p class="text-sm text-gray-500 dark:text-gray-400 dark:text-gray-500 mb-6">You will be registered with the following information. Is this okay?</p>
<div class="bg-gray-50 dark:bg-zinc-950/80 backdrop-blur-sm rounded-xl p-5 border border-gray-100 dark:border-zinc-800 shadow-inner space-y-4 text-sm">
<div class="flex justify-between items-center border-b border-gray-200 dark:border-zinc-800 pb-3">
<span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">Email Address</span>
<span class="font-bold text-gray-800 dark:text-gray-200">you@example.com</span>
</div>
<div class="flex justify-between items-center border-b border-gray-200 dark:border-zinc-800 pb-3">
<span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">氏First Name</span>
<span class="font-bold text-gray-800 dark:text-gray-200">John Doe</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-500 dark:text-gray-400 dark:text-gray-500 font-medium">Phone Number</span>
<span class="font-bold text-gray-800 dark:text-gray-200">090-1234-5678</span>
</div>
</div>
</div>
</div>
<!-- Buttons -->
<div class="flex justify-between mt-8 pt-6 border-t border-gray-100 dark:border-zinc-800 relative z-10">
<button id="msf-btn-prev" class="px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:bg-gray-50 dark:bg-zinc-950 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-all duration-300 opacity-0 pointer-events-none disabled:opacity-50 flex items-center justify-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path></svg>
Back
</button>
<button id="msf-btn-next" class="px-6 py-2.5 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(79,70,229,0.39)] hover:shadow-[0_6px_20px_rgba(79,70,229,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0">
Next
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>
</button>
</div>
</div>
</div>
<script>
(function() {
const steps = [1, 2, 3];
let currentStep = 1;
const progressBar = document.getElementById('msf-progress-bar');
const indicators = document.querySelectorAll('.msf-step-indicator');
const labels = document.querySelectorAll('.msf-step-label');
const contents = document.querySelectorAll('.msf-step-content');
const btnPrev = document.getElementById('msf-btn-prev');
const btnNext = document.getElementById('msf-btn-next');
if (!progressBar) return;
function validateStep() {
if (currentStep === steps.length) return true;
const currentContent = document.querySelector('.msf-step-content[data-step="' + currentStep + '"]');
const inputs = currentContent.querySelectorAll('input[required]');
let isValid = true;
inputs.forEach(input => {
if (!input.checkValidity()) {
input.reportValidity();
isValid = false;
}
});
return isValid;
}
function updateUI() {
const progressPercentage = ((currentStep - 1) / (steps.length - 1)) * 100;
progressBar.style.width = progressPercentage + '%';
indicators.forEach((ind, index) => {
const stepNum = index + 1;
const label = labels[index];
if (stepNum < currentStep) {
ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900";
ind.innerHTML = '<svg class="w-5 h-5 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"></path></svg>';
label.className = "msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase";
} else if (stepNum === currentStep) {
ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-indigo-600 text-white shadow-md border-4 border-white dark:border-zinc-900 ring-4 ring-indigo-100 scale-110";
ind.innerHTML = stepNum;
label.className = "msf-step-label text-xs font-bold text-indigo-600 mt-2 transition-colors duration-300 tracking-wide uppercase";
} else {
ind.className = "msf-step-indicator w-10 h-10 rounded-full flex items-center justify-center font-bold transition-all duration-500 bg-gray-200 dark:bg-zinc-800 text-gray-500 dark:text-gray-400 dark:text-gray-500 border-4 border-white dark:border-zinc-900";
ind.innerHTML = stepNum;
label.className = "msf-step-label text-xs font-medium text-gray-400 dark:text-gray-500 mt-2 transition-colors duration-300 tracking-wide uppercase";
}
});
contents.forEach((content, index) => {
const stepNum = index + 1;
if (stepNum === currentStep) {
content.className = "msf-step-content absolute w-full transition-all duration-500 transform translate-x-0 opacity-100 delay-100";
} else if (stepNum < currentStep) {
content.className = "msf-step-content absolute w-full transition-all duration-500 transform -translate-x-12 opacity-0 pointer-events-none";
} else {
content.className = "msf-step-content absolute w-full transition-all duration-500 transform translate-x-12 opacity-0 pointer-events-none";
}
});
if (currentStep === 1) {
btnPrev.className = "px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl transition-all duration-300 opacity-0 pointer-events-none absolute left-0";
} else {
btnPrev.className = "px-6 py-2.5 border border-gray-300 dark:border-zinc-700 text-gray-700 dark:text-gray-300 font-bold rounded-xl hover:bg-gray-50 dark:bg-zinc-950 hover:text-indigo-600 focus:outline-none focus:ring-2 focus:ring-gray-200 transition-all duration-300 opacity-100 flex items-center justify-center gap-2";
}
if (currentStep === steps.length) {
btnNext.innerHTML = 'Submit <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>';
btnNext.className = "px-6 py-2.5 bg-emerald-500 text-white font-bold rounded-xl hover:bg-emerald-600 focus:outline-none focus:ring-2 focus:ring-emerald-400 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(16,185,129,0.39)] hover:shadow-[0_6px_20px_rgba(16,185,129,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0";
} else {
btnNext.innerHTML = 'Next <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path></svg>';
btnNext.className = "px-6 py-2.5 bg-indigo-600 text-white font-bold rounded-xl hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-all duration-300 ml-auto shadow-[0_4px_14px_0_rgba(79,70,229,0.39)] hover:shadow-[0_6px_20px_rgba(79,70,229,0.23)] hover:-translate-y-0.5 flex items-center justify-center gap-2 active:translate-y-0";
}
}
btnPrev.addEventListener('click', () => {
if (currentStep > 1) {
currentStep--;
updateUI();
}
});
btnNext.addEventListener('click', () => {
if (!validateStep()) return;
if (currentStep < steps.length) {
currentStep++;
updateUI();
} else {
btnNext.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Submitting...';
setTimeout(() => {
alert('Form submitted successfully!');
currentStep = 1;
updateUI();
}, 1500);
}
});
updateUI();
})();
</script> Hỗ trợ trình duyệt
Hoạt động tốt với các phiên bản mới nhất của trình duyệt hiện đại (Chrome, Safari, Firefox, Edge).Các lớp tiện ích CSS Transitions và Tailwind được sử dụng cho hoạt ảnh và JavaScript thực hiện quản lý trạng thái (chỉ thay thế lớp và tính toán độ rộng thanh tiến trình), khiến nó nhẹ và nhanh.