UI Components Medium
Multi-Step Form
A multi-step onboarding form with animated step transitions, progress indicator, validation, and a final review screen.
Open in Lab
MCP
css vanilla-js
Targets: JS HTML
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0f172a;
color: #f1f5f9;
min-height: 100vh;
display: grid;
place-items: center;
padding: 2rem;
}
/* ── Card ── */
.form-wrapper {
width: min(560px, 100%);
}
.form-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 1.25rem;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* ── Progress ── */
.progress-bar {
padding: 1.75rem 2rem 0;
position: relative;
}
.progress-track {
position: absolute;
top: calc(1.75rem + 13px);
left: calc(2rem + 24px);
right: calc(2rem + 24px);
height: 2px;
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
z-index: 0;
}
.progress-line {
height: 100%;
background: linear-gradient(90deg, #38bdf8, #818cf8);
border-radius: 999px;
width: 0%;
transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.step-dots {
display: flex;
justify-content: space-between;
position: relative;
z-index: 1;
}
.step-dot {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.375rem;
background: none;
border: none;
cursor: default;
padding: 0;
}
.dot-number {
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.06);
border: 2px solid rgba(255, 255, 255, 0.12);
display: grid;
place-items: center;
font-size: 0.75rem;
font-weight: 700;
color: #475569;
transition: background 0.3s ease, border-color 0.3s ease, color 0.3s ease;
}
.step-dot.active .dot-number {
background: #38bdf8;
border-color: #38bdf8;
color: #0f172a;
}
.step-dot.completed .dot-number {
background: rgba(56, 189, 248, 0.15);
border-color: #38bdf8;
color: #38bdf8;
}
.dot-label {
font-size: 0.6875rem;
color: #475569;
font-weight: 500;
transition: color 0.3s ease;
}
.step-dot.active .dot-label {
color: #38bdf8;
}
.step-dot.completed .dot-label {
color: #64748b;
}
/* ── Steps viewport ── */
.steps-viewport {
padding: 1.75rem 2rem;
position: relative;
overflow: hidden;
min-height: 320px;
}
.step-panel {
display: none;
flex-direction: column;
gap: 1.5rem;
}
.step-panel.active {
display: flex;
animation: slideInRight 0.3s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.step-panel.slide-out-left {
display: flex;
animation: slideOutLeft 0.25s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.step-panel.slide-in-left {
display: flex;
animation: slideInLeft 0.3s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.step-panel.slide-out-right {
display: flex;
animation: slideOutRight 0.25s cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(32px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutLeft {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(-32px);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-32px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideOutRight {
from {
opacity: 1;
transform: translateX(0);
}
to {
opacity: 0;
transform: translateX(32px);
}
}
.step-header {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.step-title {
font-size: 1.25rem;
font-weight: 700;
color: #f1f5f9;
}
.step-subtitle {
font-size: 0.875rem;
color: #64748b;
}
/* ── Fields ── */
.fields {
display: flex;
flex-direction: column;
gap: 1rem;
}
.field-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.875rem;
}
.field-group {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.field-label {
font-size: 0.8125rem;
font-weight: 600;
color: #94a3b8;
}
.required {
color: #f87171;
}
.optional {
color: #475569;
font-weight: 400;
font-size: 0.75rem;
}
.field-input {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.625rem;
color: #f1f5f9;
font-size: 0.9375rem;
padding: 0.625rem 0.875rem;
font-family: inherit;
width: 100%;
transition: border-color 0.15s ease, background 0.15s ease;
}
.field-input:focus {
outline: none;
border-color: rgba(56, 189, 248, 0.5);
background: rgba(56, 189, 248, 0.04);
}
.field-input::placeholder {
color: #475569;
}
.field-input.error {
border-color: rgba(248, 113, 113, 0.5);
background: rgba(248, 113, 113, 0.04);
}
.field-select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.5'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.875rem center;
padding-right: 2.5rem;
cursor: pointer;
}
.field-error {
font-size: 0.75rem;
color: #f87171;
min-height: 1rem;
display: block;
}
/* ── Radios ── */
.radio-group {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.radio-item {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.45rem 0.875rem;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 999px;
font-size: 0.875rem;
color: #94a3b8;
cursor: pointer;
transition: all 0.15s ease;
}
.radio-item:hover {
border-color: rgba(56, 189, 248, 0.3);
color: #e2e8f0;
}
.radio-item input {
accent-color: #38bdf8;
}
.radio-item:has(input:checked) {
background: rgba(56, 189, 248, 0.1);
border-color: rgba(56, 189, 248, 0.4);
color: #38bdf8;
}
/* ── Toggle switches ── */
.toggle-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.toggle-item {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
padding: 0.625rem 0.875rem;
border-radius: 0.625rem;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.06);
transition: background 0.15s ease;
}
.toggle-item:hover {
background: rgba(255, 255, 255, 0.04);
}
.toggle-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.toggle-name {
font-size: 0.875rem;
font-weight: 500;
color: #e2e8f0;
}
.toggle-desc {
font-size: 0.75rem;
color: #475569;
}
.toggle-checkbox {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.toggle-switch {
width: 2.25rem;
height: 1.25rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 999px;
position: relative;
flex-shrink: 0;
transition: background 0.2s ease;
}
.toggle-switch::after {
content: "";
position: absolute;
width: 0.875rem;
height: 0.875rem;
background: #fff;
border-radius: 50%;
top: 3px;
left: 3px;
transition: transform 0.2s ease;
}
.toggle-checkbox:checked + .toggle-switch {
background: #38bdf8;
}
.toggle-checkbox:checked + .toggle-switch::after {
transform: translateX(1rem);
}
/* ── Newsletter checkbox ── */
.newsletter-label {
display: flex;
align-items: center;
gap: 0.625rem;
cursor: pointer;
font-size: 0.875rem;
color: #94a3b8;
}
.newsletter-label input {
width: 1rem;
height: 1rem;
accent-color: #38bdf8;
flex-shrink: 0;
}
/* ── Review card ── */
.review-card {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.review-section {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 0.75rem;
padding: 0.875rem 1rem;
}
.review-section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.625rem;
}
.review-section-title {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: #64748b;
}
.review-edit-btn {
font-size: 0.75rem;
color: #38bdf8;
background: none;
border: none;
cursor: pointer;
padding: 0;
text-decoration: underline;
font-family: inherit;
}
.review-edit-btn:hover {
color: #7dd3fc;
}
.review-row {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 1rem;
padding: 0.2rem 0;
}
.review-label {
font-size: 0.8125rem;
color: #64748b;
flex-shrink: 0;
}
.review-value {
font-size: 0.875rem;
color: #e2e8f0;
font-weight: 500;
text-align: right;
word-break: break-word;
}
/* ── Form nav ── */
.form-nav {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 1.25rem 2rem 1.75rem;
border-top: 1px solid rgba(255, 255, 255, 0.06);
}
.nav-back {
padding: 0.625rem 1.25rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0.625rem;
color: #94a3b8;
font-size: 0.9375rem;
font-weight: 500;
cursor: pointer;
font-family: inherit;
transition: background 0.15s ease, color 0.15s ease;
}
.nav-back:hover {
background: rgba(255, 255, 255, 0.07);
color: #e2e8f0;
}
.nav-back[hidden] {
display: none;
}
.nav-continue {
padding: 0.625rem 1.5rem;
background: #38bdf8;
border: none;
border-radius: 0.625rem;
color: #0f172a;
font-size: 0.9375rem;
font-weight: 700;
cursor: pointer;
font-family: inherit;
transition: background 0.15s ease;
}
.nav-continue:hover {
background: #7dd3fc;
}
/* ── Success ── */
.success-screen {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 1.25rem;
padding: 3rem 2rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
text-align: center;
animation: slideInRight 0.4s cubic-bezier(0.4, 0, 0.2, 1) both;
}
.success-screen[hidden] {
display: none;
}
.success-icon {
width: 4rem;
height: 4rem;
background: rgba(52, 211, 153, 0.15);
border: 2px solid #34d399;
border-radius: 50%;
display: grid;
place-items: center;
color: #34d399;
}
.success-title {
font-size: 1.5rem;
font-weight: 700;
color: #f1f5f9;
}
.success-msg {
font-size: 0.9375rem;
color: #64748b;
}
/* ── Responsive ── */
@media (max-width: 440px) {
.field-row {
grid-template-columns: 1fr;
}
.steps-viewport {
padding: 1.25rem 1.25rem;
}
.form-nav {
padding: 1rem 1.25rem 1.25rem;
}
.progress-bar {
padding: 1.25rem 1.25rem 0;
}
.progress-track {
left: calc(1.25rem + 24px);
right: calc(1.25rem + 24px);
}
}
@media (prefers-reduced-motion: reduce) {
.step-panel.active,
.step-panel.slide-in-left,
.step-panel.slide-out-left,
.step-panel.slide-in-right,
.step-panel.slide-out-right,
.success-screen {
animation: none;
}
.progress-line {
transition: none;
}
}(function () {
"use strict";
const TOTAL_STEPS = 4;
// ── State ──
let currentStep = 0;
const formData = {};
// ── DOM refs ──
const formCard = document.getElementById("formCard");
const successScreen = document.getElementById("successScreen");
const stepDots = document.querySelectorAll(".step-dot");
const progressLine = document.getElementById("progressLine");
const navBack = document.getElementById("navBack");
const navContinue = document.getElementById("navContinue");
const startOver = document.getElementById("startOver");
const reviewCard = document.getElementById("reviewCard");
if (!formCard) return;
// ── Validation rules per step ──
const validators = {
0: () => {
const email = document.getElementById("email");
const pw = document.getElementById("password");
const cpw = document.getElementById("confirmPassword");
let valid = true;
clearErrors();
if (!email.value.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
setError(email, "email-error", "Please enter a valid email address.");
valid = false;
}
if (pw.value.length < 8) {
setError(pw, "password-error", "Password must be at least 8 characters.");
valid = false;
}
if (cpw.value !== pw.value) {
setError(cpw, "confirmPassword-error", "Passwords do not match.");
valid = false;
}
return valid;
},
1: () => {
const first = document.getElementById("firstName");
const last = document.getElementById("lastName");
let valid = true;
clearErrors();
if (!first.value.trim()) {
setError(first, "firstName-error", "First name is required.");
valid = false;
}
if (!last.value.trim()) {
setError(last, "lastName-error", "Last name is required.");
valid = false;
}
return valid;
},
2: () => {
const role = document.getElementById("role");
const teamSize = document.querySelector('input[name="teamSize"]:checked');
let valid = true;
clearErrors();
if (!role.value) {
setError(role, "role-error", "Please select your role.");
valid = false;
}
if (!teamSize) {
const errEl = document.getElementById("teamSize-error");
if (errEl) errEl.textContent = "Please select your team size.";
valid = false;
}
return valid;
},
3: () => true,
};
function setError(input, errorId, msg) {
input.classList.add("error");
const el = document.getElementById(errorId);
if (el) el.textContent = msg;
input.addEventListener("input", function clearSelf() {
input.classList.remove("error");
const errEl = document.getElementById(errorId);
if (errEl) errEl.textContent = "";
input.removeEventListener("input", clearSelf);
});
}
function clearErrors() {
document.querySelectorAll(".field-input.error").forEach((el) => el.classList.remove("error"));
document.querySelectorAll(".field-error").forEach((el) => {
el.textContent = "";
});
}
// ── Collect form data ──
function collectStepData(step) {
switch (step) {
case 0:
formData.email = document.getElementById("email").value.trim();
formData.password = document.getElementById("password").value;
break;
case 1:
formData.firstName = document.getElementById("firstName").value.trim();
formData.lastName = document.getElementById("lastName").value.trim();
formData.phone = document.getElementById("phone").value.trim();
formData.company = document.getElementById("company").value.trim();
break;
case 2:
formData.role = document.getElementById("role").value;
formData.teamSize =
(document.querySelector('input[name="teamSize"]:checked') || {}).value || "";
formData.darkMode = document.querySelector('input[name="darkMode"]').checked;
formData.betaFeatures = document.querySelector('input[name="betaFeatures"]').checked;
formData.analytics = document.querySelector('input[name="analytics"]').checked;
formData.newsletter = document.getElementById("newsletter").checked;
break;
}
}
// ── Build review ──
function buildReview() {
const sections = [
{
title: "Account Info",
step: 0,
rows: [
{ label: "Email", value: formData.email || "—" },
{ label: "Password", value: "••••••••" },
],
},
{
title: "Personal Details",
step: 1,
rows: [
{
label: "Full Name",
value: [formData.firstName, formData.lastName].filter(Boolean).join(" ") || "—",
},
{ label: "Phone", value: formData.phone || "Not provided" },
{ label: "Company", value: formData.company || "Not provided" },
],
},
{
title: "Preferences",
step: 2,
rows: [
{ label: "Role", value: formData.role || "—" },
{ label: "Team Size", value: formData.teamSize || "—" },
{ label: "Dark Mode", value: formData.darkMode ? "Enabled" : "Disabled" },
{ label: "Beta Features", value: formData.betaFeatures ? "Enabled" : "Disabled" },
{ label: "Analytics", value: formData.analytics ? "Enabled" : "Disabled" },
{ label: "Newsletter", value: formData.newsletter ? "Yes" : "No" },
],
},
];
reviewCard.innerHTML = sections
.map(
(sec) => `
<div class="review-section">
<div class="review-section-header">
<span class="review-section-title">${sec.title}</span>
<button class="review-edit-btn" data-goto="${sec.step}">Edit</button>
</div>
${sec.rows
.map(
(row) => `
<div class="review-row">
<span class="review-label">${row.label}</span>
<span class="review-value">${row.value}</span>
</div>
`
)
.join("")}
</div>
`
)
.join("");
reviewCard.querySelectorAll(".review-edit-btn").forEach((btn) => {
btn.addEventListener("click", () => {
const target = Number(btn.dataset.goto);
goToStep(target, "back");
});
});
}
// ── Step navigation ──
function goToStep(next, direction) {
const prev = currentStep;
const prevEl = document.getElementById(`step${prev}`);
const nextEl = document.getElementById(`step${next}`);
if (!prevEl || !nextEl) return;
// Outgoing animation
const outClass = direction === "forward" ? "slide-out-left" : "slide-out-right";
const inClass = direction === "forward" ? "active" : "slide-in-left";
prevEl.classList.remove("active");
prevEl.classList.add(outClass);
prevEl.addEventListener(
"animationend",
() => {
prevEl.classList.remove(outClass);
prevEl.style.display = "none";
},
{ once: true }
);
nextEl.style.display = "flex";
nextEl.classList.add(direction === "forward" ? "active" : "slide-in-left");
if (direction !== "forward") {
nextEl.addEventListener(
"animationend",
() => {
nextEl.classList.remove("slide-in-left");
nextEl.classList.add("active");
},
{ once: true }
);
}
currentStep = next;
updateProgress();
}
function updateProgress() {
// Progress line width
const pct = currentStep === 0 ? 0 : (currentStep / (TOTAL_STEPS - 1)) * 100;
progressLine.style.width = `${pct}%`;
// Dots
stepDots.forEach((dot, i) => {
dot.classList.remove("active", "completed");
if (i === currentStep) dot.classList.add("active");
else if (i < currentStep) dot.classList.add("completed");
});
// Nav buttons
navBack.hidden = currentStep === 0;
navContinue.textContent = currentStep === TOTAL_STEPS - 1 ? "Create Account" : "Continue";
}
// ── Navigation handlers ──
navContinue.addEventListener("click", () => {
if (currentStep < TOTAL_STEPS - 1) {
const valid = (validators[currentStep] || (() => true))();
if (!valid) return;
collectStepData(currentStep);
if (currentStep === TOTAL_STEPS - 2) buildReview();
goToStep(currentStep + 1, "forward");
} else {
// Submit
formCard.hidden = true;
successScreen.hidden = false;
}
});
navBack.addEventListener("click", () => {
if (currentStep > 0) {
clearErrors();
goToStep(currentStep - 1, "back");
}
});
// ── Start over ──
startOver.addEventListener("click", () => {
// Reset form inputs
document.querySelectorAll(".field-input").forEach((el) => {
if (el.type === "checkbox" || el.type === "radio") {
el.checked = false;
} else el.value = "";
});
// Re-check defaults
const darkModeEl = document.querySelector('input[name="darkMode"]');
const analyticsEl = document.querySelector('input[name="analytics"]');
if (darkModeEl) darkModeEl.checked = true;
if (analyticsEl) analyticsEl.checked = true;
Object.keys(formData).forEach((k) => delete formData[k]);
clearErrors();
currentStep = 0;
updateProgress();
// Reset all step panels
document.querySelectorAll(".step-panel").forEach((el, i) => {
el.classList.remove(
"active",
"slide-in-left",
"slide-out-left",
"slide-in-right",
"slide-out-right"
);
el.style.display = "";
if (i === 0) el.classList.add("active");
});
successScreen.hidden = true;
formCard.hidden = false;
});
// ── Init ──
updateProgress();
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Multi-Step Form</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="form-wrapper">
<div class="form-card" id="formCard">
<!-- Progress indicator -->
<div class="progress-bar" id="progressBar" aria-label="Form progress">
<div class="progress-track">
<div class="progress-line" id="progressLine"></div>
</div>
<div class="step-dots" id="stepDots">
<button class="step-dot active" data-step="0" aria-label="Step 1: Account Info">
<span class="dot-number">1</span>
<span class="dot-label">Account</span>
</button>
<button class="step-dot" data-step="1" aria-label="Step 2: Personal Details">
<span class="dot-number">2</span>
<span class="dot-label">Personal</span>
</button>
<button class="step-dot" data-step="2" aria-label="Step 3: Preferences">
<span class="dot-number">3</span>
<span class="dot-label">Prefs</span>
</button>
<button class="step-dot" data-step="3" aria-label="Step 4: Review">
<span class="dot-number">4</span>
<span class="dot-label">Review</span>
</button>
</div>
</div>
<!-- Steps container (overflow hidden, slides) -->
<div class="steps-viewport" id="stepsViewport">
<!-- Step 1: Account Info -->
<div class="step-panel active" id="step0" data-step="0">
<div class="step-header">
<h2 class="step-title">Create your account</h2>
<p class="step-subtitle">Start with your login credentials</p>
</div>
<div class="fields">
<div class="field-group">
<label class="field-label" for="email">Email address <span class="required">*</span></label>
<input type="email" id="email" name="email" class="field-input" placeholder="you@company.com" autocomplete="email" />
<span class="field-error" id="email-error"></span>
</div>
<div class="field-group">
<label class="field-label" for="password">Password <span class="required">*</span></label>
<input type="password" id="password" name="password" class="field-input" placeholder="Min. 8 characters" autocomplete="new-password" />
<span class="field-error" id="password-error"></span>
</div>
<div class="field-group">
<label class="field-label" for="confirmPassword">Confirm password <span class="required">*</span></label>
<input type="password" id="confirmPassword" name="confirmPassword" class="field-input" placeholder="Repeat your password" autocomplete="new-password" />
<span class="field-error" id="confirmPassword-error"></span>
</div>
</div>
</div>
<!-- Step 2: Personal Details -->
<div class="step-panel" id="step1" data-step="1">
<div class="step-header">
<h2 class="step-title">Personal details</h2>
<p class="step-subtitle">Tell us a little about yourself</p>
</div>
<div class="fields">
<div class="field-row">
<div class="field-group">
<label class="field-label" for="firstName">First name <span class="required">*</span></label>
<input type="text" id="firstName" name="firstName" class="field-input" placeholder="Alice" autocomplete="given-name" />
<span class="field-error" id="firstName-error"></span>
</div>
<div class="field-group">
<label class="field-label" for="lastName">Last name <span class="required">*</span></label>
<input type="text" id="lastName" name="lastName" class="field-input" placeholder="Martin" autocomplete="family-name" />
<span class="field-error" id="lastName-error"></span>
</div>
</div>
<div class="field-group">
<label class="field-label" for="phone">Phone number</label>
<input type="tel" id="phone" name="phone" class="field-input" placeholder="+1 (555) 000-0000" autocomplete="tel" />
</div>
<div class="field-group">
<label class="field-label" for="company">Company name <span class="optional">(optional)</span></label>
<input type="text" id="company" name="company" class="field-input" placeholder="Acme Corp." autocomplete="organization" />
</div>
</div>
</div>
<!-- Step 3: Preferences -->
<div class="step-panel" id="step2" data-step="2">
<div class="step-header">
<h2 class="step-title">Your preferences</h2>
<p class="step-subtitle">Personalise your experience</p>
</div>
<div class="fields">
<div class="field-group">
<label class="field-label" for="role">Your role <span class="required">*</span></label>
<select id="role" name="role" class="field-input field-select">
<option value="">Select your role…</option>
<option value="Developer">Developer</option>
<option value="Designer">Designer</option>
<option value="Product Manager">Product Manager</option>
<option value="Marketing">Marketing</option>
<option value="Other">Other</option>
</select>
<span class="field-error" id="role-error"></span>
</div>
<div class="field-group">
<span class="field-label">Team size <span class="required">*</span></span>
<div class="radio-group" id="teamSizeGroup">
<label class="radio-item"><input type="radio" name="teamSize" value="Solo" /> Solo</label>
<label class="radio-item"><input type="radio" name="teamSize" value="2–10" /> 2–10</label>
<label class="radio-item"><input type="radio" name="teamSize" value="11–50" /> 11–50</label>
<label class="radio-item"><input type="radio" name="teamSize" value="50+" /> 50+</label>
</div>
<span class="field-error" id="teamSize-error"></span>
</div>
<div class="field-group">
<label class="field-label">Feature toggles</label>
<div class="toggle-list">
<label class="toggle-item">
<div class="toggle-info">
<span class="toggle-name">Dark Mode</span>
<span class="toggle-desc">Use dark interface by default</span>
</div>
<input type="checkbox" name="darkMode" class="toggle-checkbox" checked />
<div class="toggle-switch"></div>
</label>
<label class="toggle-item">
<div class="toggle-info">
<span class="toggle-name">Beta Features</span>
<span class="toggle-desc">Access experimental functionality</span>
</div>
<input type="checkbox" name="betaFeatures" class="toggle-checkbox" />
<div class="toggle-switch"></div>
</label>
<label class="toggle-item">
<div class="toggle-info">
<span class="toggle-name">Analytics</span>
<span class="toggle-desc">Help improve with usage data</span>
</div>
<input type="checkbox" name="analytics" class="toggle-checkbox" checked />
<div class="toggle-switch"></div>
</label>
</div>
</div>
<label class="newsletter-label">
<input type="checkbox" name="newsletter" id="newsletter" />
<span>Send me product updates and tips</span>
</label>
</div>
</div>
<!-- Step 4: Review -->
<div class="step-panel" id="step3" data-step="3">
<div class="step-header">
<h2 class="step-title">Review & submit</h2>
<p class="step-subtitle">Check your details before creating your account</p>
</div>
<div class="review-card" id="reviewCard"></div>
</div>
</div>
<!-- Navigation -->
<div class="form-nav" id="formNav">
<button class="nav-back" id="navBack" hidden>Back</button>
<button class="nav-continue" id="navContinue">Continue</button>
</div>
</div>
<!-- Success screen -->
<div class="success-screen" id="successScreen" hidden>
<div class="success-icon" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="20 6 9 17 4 12"/>
</svg>
</div>
<h2 class="success-title">Account created!</h2>
<p class="success-msg">Welcome aboard. Your account is ready to use.</p>
<button class="nav-continue" id="startOver">Start Over</button>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Multi-Step Form
A polished four-step onboarding wizard with slide animations between steps, inline validation, a review screen that summarises all entered data, and a success state on submit. Each step validates required fields before allowing progression.
Features
- Four distinct steps: Account Info, Personal Details, Preferences, Review & Submit
- Animated slide-in/out transitions with forward and backward directional logic
- Progress indicator with connected dots showing the current and completed steps
- Inline field validation with highlighted error borders and messages
- Final review card with per-section “Edit” links to jump back to any step