Pages Medium
Onboarding Page
A multi-step onboarding / welcome flow with progress indicator, profile setup, preferences selection, team invite, and completion celebration. No libraries.
Open in Lab
MCP
vanilla-js css
Targets: JS HTML
Code
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--ob-primary: #6366f1;
--ob-primary-dark: #4f46e5;
--ob-primary-light: #eef2ff;
--ob-green: #22c55e;
--ob-green-light: #dcfce7;
--ob-gray-50: #f9fafb;
--ob-gray-100: #f3f4f6;
--ob-gray-200: #e5e7eb;
--ob-gray-300: #d1d5db;
--ob-gray-400: #9ca3af;
--ob-gray-500: #6b7280;
--ob-gray-600: #4b5563;
--ob-gray-700: #374151;
--ob-gray-800: #1f2937;
--ob-gray-900: #111827;
--ob-red: #ef4444;
--ob-radius: 8px;
--ob-radius-lg: 16px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #eef2ff 0%, #e0e7ff 30%, #dbeafe 60%, #ede9fe 100%);
padding: 24px;
line-height: 1.5;
color: var(--ob-gray-800);
}
button {
cursor: pointer;
border: none;
background: none;
font: inherit;
color: inherit;
}
input,
select {
font: inherit;
}
/* Card */
.ob-card {
width: 100%;
max-width: 640px;
background: #fff;
border-radius: var(--ob-radius-lg);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.08), 0 4px 16px rgba(0, 0, 0, 0.04);
padding: 48px;
position: relative;
overflow: hidden;
}
/* Progress Steps */
.ob-progress {
margin-bottom: 40px;
}
.ob-step-row {
display: flex;
align-items: flex-start;
justify-content: center;
}
.ob-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
position: relative;
z-index: 1;
}
.ob-step-circle {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
border: 2px solid var(--ob-gray-300);
color: var(--ob-gray-400);
background: #fff;
transition: all .3s ease;
}
.ob-step.active .ob-step-circle {
border-color: var(--ob-primary);
background: var(--ob-primary);
color: #fff;
}
.ob-step.completed .ob-step-circle {
border-color: var(--ob-green);
background: var(--ob-green);
color: #fff;
}
.ob-step-label {
font-size: 12px;
font-weight: 500;
color: var(--ob-gray-400);
white-space: nowrap;
transition: color .3s;
}
.ob-step.active .ob-step-label {
color: var(--ob-primary);
}
.ob-step.completed .ob-step-label {
color: var(--ob-green);
}
.ob-step-line {
flex: 1;
height: 2px;
background: var(--ob-gray-200);
margin-top: 18px;
min-width: 32px;
position: relative;
overflow: hidden;
}
.ob-step-line-fill {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 0%;
background: var(--ob-green);
transition: width .4s ease;
}
.ob-step-line.filled .ob-step-line-fill {
width: 100%;
}
/* Panels */
.ob-panel {
display: none;
}
.ob-panel.active {
display: block;
animation: obFadeIn .35s ease;
}
@keyframes obFadeIn {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Headings */
.ob-heading {
font-size: 22px;
font-weight: 700;
color: var(--ob-gray-900);
margin-bottom: 8px;
text-align: center;
}
.ob-subtext {
font-size: 14px;
color: var(--ob-gray-500);
text-align: center;
margin-bottom: 32px;
}
/* Avatar */
.ob-avatar-wrap {
display: flex;
justify-content: center;
margin-bottom: 28px;
}
.ob-avatar {
width: 96px;
height: 96px;
border-radius: 50%;
border: 2px dashed var(--ob-gray-300);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
overflow: hidden;
position: relative;
transition: border-color .2s, background .2s;
}
.ob-avatar:hover {
border-color: var(--ob-primary);
background: var(--ob-primary-light);
}
.ob-avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
display: none;
}
.ob-avatar-img.visible {
display: block;
}
.ob-avatar-img.visible + .ob-avatar-placeholder {
display: none;
}
.ob-avatar-placeholder {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
color: var(--ob-gray-400);
font-size: 11px;
pointer-events: none;
}
/* Form Fields */
.ob-field {
margin-bottom: 20px;
}
.ob-label {
display: block;
font-size: 13px;
font-weight: 600;
color: var(--ob-gray-700);
margin-bottom: 6px;
}
.ob-required {
color: var(--ob-red);
}
.ob-optional {
color: var(--ob-gray-400);
font-weight: 400;
}
.ob-input {
width: 100%;
padding: 10px 14px;
border: 1px solid var(--ob-gray-300);
border-radius: var(--ob-radius);
font-size: 14px;
color: var(--ob-gray-800);
outline: none;
transition: border-color .15s, box-shadow .15s;
background: #fff;
}
.ob-input:focus {
border-color: var(--ob-primary);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);
}
.ob-input.error {
border-color: var(--ob-red);
}
.ob-select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236B7280' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 36px;
}
.ob-error {
display: none;
font-size: 12px;
color: var(--ob-red);
margin-top: 6px;
}
.ob-error.visible {
display: block;
}
/* Topics Grid */
.ob-topics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 4px;
}
.ob-topic {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px 8px;
border: 2px solid var(--ob-gray-200);
border-radius: 12px;
background: #fff;
position: relative;
transition: border-color .15s, background .15s, transform .1s;
}
.ob-topic:hover {
border-color: var(--ob-primary);
background: var(--ob-primary-light);
}
.ob-topic.selected {
border-color: var(--ob-primary);
background: var(--ob-primary-light);
}
.ob-topic:active {
transform: scale(0.97);
}
.ob-topic-icon {
color: var(--ob-gray-500);
transition: color .15s;
}
.ob-topic.selected .ob-topic-icon {
color: var(--ob-primary);
}
.ob-topic-label {
font-size: 12px;
font-weight: 500;
color: var(--ob-gray-600);
text-align: center;
}
.ob-topic.selected .ob-topic-label {
color: var(--ob-primary-dark);
}
.ob-topic-check {
position: absolute;
top: 6px;
right: 6px;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--ob-primary);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transform: scale(0.5);
transition: opacity .15s, transform .15s;
color: #fff;
}
.ob-topic.selected .ob-topic-check {
opacity: 1;
transform: scale(1);
}
/* Invite */
.ob-invite-input-row {
display: flex;
gap: 8px;
margin-bottom: 4px;
}
.ob-invite-input-row .ob-input {
flex: 1;
}
.ob-invite-input-row .ob-btn {
flex-shrink: 0;
}
.ob-invite-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 16px;
min-height: 20px;
}
.ob-invite-tag {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: var(--ob-primary-light);
color: var(--ob-primary-dark);
border-radius: 9999px;
font-size: 13px;
font-weight: 500;
animation: obFadeIn .2s ease;
}
.ob-invite-remove {
width: 18px;
height: 18px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
color: var(--ob-primary);
transition: background .12s, color .12s;
}
.ob-invite-remove:hover {
background: var(--ob-primary);
color: #fff;
}
/* Navigation */
.ob-nav {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 32px;
gap: 12px;
}
.ob-nav-right {
display: flex;
align-items: center;
gap: 16px;
}
.ob-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 10px 24px;
border-radius: var(--ob-radius);
font-size: 14px;
font-weight: 600;
transition: background .15s, border-color .15s, transform .1s;
}
.ob-btn:active {
transform: scale(0.98);
}
.ob-btn-primary {
background: var(--ob-primary);
color: #fff;
}
.ob-btn-primary:hover {
background: var(--ob-primary-dark);
}
.ob-btn-outline {
border: 1px solid var(--ob-gray-300);
color: var(--ob-gray-700);
background: #fff;
}
.ob-btn-outline:hover {
background: var(--ob-gray-50);
border-color: var(--ob-gray-400);
}
.ob-skip {
font-size: 13px;
color: var(--ob-gray-500);
text-decoration: underline;
text-underline-offset: 2px;
}
.ob-skip:hover {
color: var(--ob-primary);
}
/* Step 4: Complete */
.ob-confetti-area {
position: absolute;
inset: 0;
pointer-events: none;
overflow: hidden;
}
.ob-confetti-piece {
position: absolute;
width: 8px;
height: 8px;
border-radius: 2px;
top: -10px;
animation: obConfettiFall linear forwards;
}
@keyframes obConfettiFall {
0% {
transform: translateY(0) rotate(0deg);
opacity: 1;
}
100% {
transform: translateY(600px) rotate(720deg);
opacity: 0;
}
}
.ob-complete-content {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
position: relative;
z-index: 1;
}
.ob-check-circle {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--ob-green);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 24px;
animation: obPop .4s cubic-bezier(0.34, 1.56, 0.64, 1) .1s both;
}
@keyframes obPop {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
.ob-heading-complete {
font-size: 26px;
margin-bottom: 8px;
}
.ob-welcome-msg {
font-size: 15px;
color: var(--ob-gray-500);
margin-bottom: 24px;
text-align: center;
}
.ob-summary {
width: 100%;
max-width: 360px;
margin-bottom: 32px;
font-size: 13px;
color: var(--ob-gray-600);
}
.ob-summary-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid var(--ob-gray-100);
}
.ob-summary-label {
color: var(--ob-gray-500);
}
.ob-summary-value {
font-weight: 500;
color: var(--ob-gray-800);
}
.ob-btn-cta {
width: 100%;
max-width: 320px;
padding: 14px 32px;
font-size: 16px;
font-weight: 700;
color: #fff;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
border-radius: 12px;
box-shadow: 0 4px 14px rgba(99, 102, 241, 0.35);
transition: box-shadow .2s, transform .15s;
}
.ob-btn-cta:hover {
box-shadow: 0 6px 20px rgba(99, 102, 241, 0.45);
transform: translateY(-1px);
}
.ob-btn-cta:active {
transform: translateY(0);
}
/* Responsive */
@media (max-width: 640px) {
.ob-card {
padding: 28px 20px;
}
.ob-topics {
grid-template-columns: repeat(2, 1fr);
}
.ob-step-label {
display: none;
}
.ob-heading {
font-size: 20px;
}
.ob-nav-right {
flex-direction: column;
align-items: flex-end;
gap: 8px;
}
}(function () {
"use strict";
/* ── State ── */
var currentStep = 1;
var totalSteps = 4;
var selectedTopics = new Set();
var invitedEmails = [];
/* ── DOM Refs ── */
var panels = document.querySelectorAll(".ob-panel");
var steps = document.querySelectorAll(".ob-step");
var lines = document.querySelectorAll(".ob-step-line");
var nameInput = document.getElementById("ob-name");
var nameError = document.getElementById("ob-name-error");
var roleSelect = document.getElementById("ob-role");
var companyInput = document.getElementById("ob-company");
var topicsContainer = document.getElementById("ob-topics");
var topicsError = document.getElementById("ob-topics-error");
var frequencySelect = document.getElementById("ob-frequency");
var emailInput = document.getElementById("ob-invite-email");
var emailError = document.getElementById("ob-email-error");
var inviteList = document.getElementById("ob-invite-list");
var inviteAddBtn = document.getElementById("ob-invite-add");
var avatarInput = document.getElementById("ob-avatar-input");
var avatarPreview = document.getElementById("ob-avatar-preview");
var avatarPlaceholder = document.getElementById("ob-avatar-placeholder");
var welcomeMsg = document.getElementById("ob-welcome-msg");
var summaryEl = document.getElementById("ob-summary");
var confettiArea = document.getElementById("ob-confetti-area");
/* ── Step Navigation ── */
function goToStep(step) {
if (step < 1 || step > totalSteps) return;
// Validate before advancing
if (step > currentStep) {
if (!validateStep(currentStep)) return;
}
currentStep = step;
updateProgress();
showPanel(step);
if (step === totalSteps) {
buildCompletion();
}
}
function showPanel(step) {
panels.forEach(function (p) {
p.classList.remove("active");
});
var target = document.getElementById("step-" + step);
if (target) target.classList.add("active");
}
function updateProgress() {
steps.forEach(function (s, i) {
var stepNum = i + 1;
s.classList.remove("active", "completed");
if (stepNum === currentStep) {
s.classList.add("active");
} else if (stepNum < currentStep) {
s.classList.add("completed");
s.querySelector(".ob-step-circle").innerHTML =
'<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="#fff" stroke-width="2.5"><polyline points="3,8 6.5,11.5 13,5"/></svg>';
} else {
s.querySelector(".ob-step-circle").textContent = stepNum;
}
});
lines.forEach(function (line, i) {
if (i < currentStep - 1) {
line.classList.add("filled");
} else {
line.classList.remove("filled");
}
});
}
function validateStep(step) {
switch (step) {
case 1:
var name = nameInput.value.trim();
if (!name) {
nameInput.classList.add("error");
nameError.classList.add("visible");
nameInput.focus();
return false;
}
nameInput.classList.remove("error");
nameError.classList.remove("visible");
return true;
case 2:
if (selectedTopics.size === 0) {
topicsError.classList.add("visible");
return false;
}
topicsError.classList.remove("visible");
return true;
case 3:
return true;
default:
return true;
}
}
// Next / Back buttons
document.querySelectorAll("[data-next]").forEach(function (btn) {
btn.addEventListener("click", function () {
goToStep(parseInt(btn.dataset.next, 10));
});
});
document.querySelectorAll("[data-prev]").forEach(function (btn) {
btn.addEventListener("click", function () {
goToStep(parseInt(btn.dataset.prev, 10));
});
});
// Clear validation on input
nameInput.addEventListener("input", function () {
nameInput.classList.remove("error");
nameError.classList.remove("visible");
});
/* ── Avatar Upload ── */
avatarInput.addEventListener("change", function () {
var file = avatarInput.files[0];
if (!file) return;
var reader = new FileReader();
reader.onload = function (e) {
avatarPreview.src = e.target.result;
avatarPreview.classList.add("visible");
};
reader.readAsDataURL(file);
});
/* ── Topics ── */
topicsContainer.addEventListener("click", function (e) {
var topic = e.target.closest(".ob-topic");
if (!topic) return;
var key = topic.dataset.topic;
if (selectedTopics.has(key)) {
selectedTopics.delete(key);
topic.classList.remove("selected");
} else {
selectedTopics.add(key);
topic.classList.add("selected");
}
if (selectedTopics.size > 0) {
topicsError.classList.remove("visible");
}
});
/* ── Email Invites ── */
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function addInvite() {
var email = emailInput.value.trim();
if (!email || !isValidEmail(email)) {
emailError.classList.add("visible");
emailInput.classList.add("error");
emailInput.focus();
return;
}
if (invitedEmails.indexOf(email) !== -1) {
emailInput.value = "";
return;
}
emailError.classList.remove("visible");
emailInput.classList.remove("error");
invitedEmails.push(email);
emailInput.value = "";
renderInvites();
emailInput.focus();
}
function removeInvite(email) {
invitedEmails = invitedEmails.filter(function (e) {
return e !== email;
});
renderInvites();
}
function renderInvites() {
inviteList.innerHTML = "";
invitedEmails.forEach(function (email) {
var tag = document.createElement("span");
tag.className = "ob-invite-tag";
tag.innerHTML =
email + '<button class="ob-invite-remove" data-email="' + email + '">×</button>';
inviteList.appendChild(tag);
});
}
inviteAddBtn.addEventListener("click", addInvite);
emailInput.addEventListener("keydown", function (e) {
if (e.key === "Enter") {
e.preventDefault();
addInvite();
}
});
emailInput.addEventListener("input", function () {
emailError.classList.remove("visible");
emailInput.classList.remove("error");
});
inviteList.addEventListener("click", function (e) {
var btn = e.target.closest(".ob-invite-remove");
if (btn) removeInvite(btn.dataset.email);
});
/* ── Skip Team ── */
document.getElementById("ob-skip-team").addEventListener("click", function () {
goToStep(4);
});
/* ── Completion ── */
function buildCompletion() {
var name = nameInput.value.trim() || "there";
welcomeMsg.textContent = "Welcome to the platform, " + name + "!";
// Build summary
var summaryHtml = "";
var role = roleSelect.value;
if (role) {
var roleText = roleSelect.options[roleSelect.selectedIndex].text;
summaryHtml +=
'<div class="ob-summary-item"><span class="ob-summary-label">Role</span><span class="ob-summary-value">' +
roleText +
"</span></div>";
}
var company = companyInput.value.trim();
if (company) {
summaryHtml +=
'<div class="ob-summary-item"><span class="ob-summary-label">Company</span><span class="ob-summary-value">' +
company +
"</span></div>";
}
if (selectedTopics.size > 0) {
var topicLabels = [];
selectedTopics.forEach(function (key) {
var el = topicsContainer.querySelector('[data-topic="' + key + '"] .ob-topic-label');
if (el) topicLabels.push(el.textContent);
});
summaryHtml +=
'<div class="ob-summary-item"><span class="ob-summary-label">Interests</span><span class="ob-summary-value">' +
topicLabels.join(", ") +
"</span></div>";
}
var freqText = frequencySelect.options[frequencySelect.selectedIndex].text;
summaryHtml +=
'<div class="ob-summary-item"><span class="ob-summary-label">Email</span><span class="ob-summary-value">' +
freqText +
"</span></div>";
if (invitedEmails.length > 0) {
summaryHtml +=
'<div class="ob-summary-item"><span class="ob-summary-label">Invites sent</span><span class="ob-summary-value">' +
invitedEmails.length +
" teammate" +
(invitedEmails.length > 1 ? "s" : "") +
"</span></div>";
}
summaryEl.innerHTML = summaryHtml;
// Launch confetti
launchConfetti();
}
/* ── Confetti ── */
function launchConfetti() {
confettiArea.innerHTML = "";
var colors = [
"#6366f1",
"#8b5cf6",
"#ec4899",
"#f59e0b",
"#22c55e",
"#3b82f6",
"#ef4444",
"#14b8a6",
];
for (var i = 0; i < 60; i++) {
var piece = document.createElement("div");
piece.className = "ob-confetti-piece";
piece.style.left = Math.random() * 100 + "%";
piece.style.width = Math.random() * 8 + 4 + "px";
piece.style.height = Math.random() * 8 + 4 + "px";
piece.style.background = colors[Math.floor(Math.random() * colors.length)];
piece.style.animationDuration = Math.random() * 2 + 1.5 + "s";
piece.style.animationDelay = Math.random() * 0.8 + "s";
piece.style.borderRadius = Math.random() > 0.5 ? "50%" : "2px";
confettiArea.appendChild(piece);
}
}
/* ── Dashboard CTA ── */
document.getElementById("ob-go-dashboard").addEventListener("click", function () {
alert("Redirecting to dashboard...");
});
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Onboarding</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="ob">
<div class="ob-card">
<!-- Progress Bar -->
<div class="ob-progress">
<div class="ob-step-row">
<div class="ob-step active" data-step="1">
<div class="ob-step-circle">1</div>
<span class="ob-step-label">Profile</span>
</div>
<div class="ob-step-line"><div class="ob-step-line-fill"></div></div>
<div class="ob-step" data-step="2">
<div class="ob-step-circle">2</div>
<span class="ob-step-label">Preferences</span>
</div>
<div class="ob-step-line"><div class="ob-step-line-fill"></div></div>
<div class="ob-step" data-step="3">
<div class="ob-step-circle">3</div>
<span class="ob-step-label">Team</span>
</div>
<div class="ob-step-line"><div class="ob-step-line-fill"></div></div>
<div class="ob-step" data-step="4">
<div class="ob-step-circle">4</div>
<span class="ob-step-label">Complete</span>
</div>
</div>
</div>
<!-- Step 1: Profile -->
<div class="ob-panel active" id="step-1">
<h2 class="ob-heading">Welcome! Let's set up your profile</h2>
<p class="ob-subtext">Tell us a bit about yourself to get started.</p>
<div class="ob-avatar-wrap">
<label class="ob-avatar" id="ob-avatar-label">
<input type="file" accept="image/*" id="ob-avatar-input" hidden />
<img id="ob-avatar-preview" class="ob-avatar-img" src="" alt="" />
<div class="ob-avatar-placeholder" id="ob-avatar-placeholder">
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" stroke="currentColor" stroke-width="2">
<rect x="4" y="8" width="24" height="18" rx="3"/>
<circle cx="16" cy="14" r="3"/>
<line x1="16" y1="4" x2="16" y2="8"/>
</svg>
<span>Upload photo</span>
</div>
</label>
</div>
<div class="ob-field">
<label class="ob-label" for="ob-name">Display Name <span class="ob-required">*</span></label>
<input type="text" id="ob-name" class="ob-input" placeholder="Your full name" required />
<span class="ob-error" id="ob-name-error">Please enter your name</span>
</div>
<div class="ob-field">
<label class="ob-label" for="ob-role">Role</label>
<select id="ob-role" class="ob-input ob-select">
<option value="" disabled selected>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>
</div>
<div class="ob-field">
<label class="ob-label" for="ob-company">Company / Organization <span class="ob-optional">(optional)</span></label>
<input type="text" id="ob-company" class="ob-input" placeholder="Your company name" />
</div>
<div class="ob-nav">
<div></div>
<button class="ob-btn ob-btn-primary" data-next="2">Next</button>
</div>
</div>
<!-- Step 2: Preferences -->
<div class="ob-panel" id="step-2">
<h2 class="ob-heading">What are you interested in?</h2>
<p class="ob-subtext">Select at least one topic to personalize your experience.</p>
<div class="ob-topics" id="ob-topics">
<button class="ob-topic" data-topic="design-systems">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>
</svg>
</div>
<span class="ob-topic-label">Design Systems</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="frontend">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16,18 22,12 16,6"/><polyline points="8,6 2,12 8,18"/>
</svg>
</div>
<span class="ob-topic-label">Frontend Dev</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="backend">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="2" y="3" width="20" height="6" rx="2"/><rect x="2" y="15" width="20" height="6" rx="2"/>
<circle cx="6" cy="6" r="1" fill="currentColor"/><circle cx="6" cy="18" r="1" fill="currentColor"/>
</svg>
</div>
<span class="ob-topic-label">Backend Dev</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="devops">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="9"/><path d="M12 3a9 9 0 0 1 0 18"/>
<line x1="12" y1="3" x2="12" y2="21"/><line x1="3" y1="12" x2="21" y2="12"/>
</svg>
</div>
<span class="ob-topic-label">DevOps</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="ai-ml">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 2v4m0 12v4m-7-10H1m22 0h-4m-2.3-5.7l2.8-2.8M4.5 19.5l2.8-2.8m0-9.4L4.5 4.5m15 15l-2.8-2.8"/>
<circle cx="12" cy="12" r="4"/>
</svg>
</div>
<span class="ob-topic-label">AI / ML</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="mobile">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="6" y="2" width="12" height="20" rx="3"/><line x1="10" y1="18" x2="14" y2="18"/>
</svg>
</div>
<span class="ob-topic-label">Mobile Dev</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="data-science">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="14" width="4" height="7"/><rect x="10" y="8" width="4" height="13"/><rect x="17" y="3" width="4" height="18"/>
</svg>
</div>
<span class="ob-topic-label">Data Science</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
<button class="ob-topic" data-topic="open-source">
<div class="ob-topic-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><path d="M12 2C8 6 8 18 12 22M12 2c4 4 4 16 0 20"/>
<line x1="2" y1="12" x2="22" y2="12"/>
</svg>
</div>
<span class="ob-topic-label">Open Source</span>
<div class="ob-topic-check">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="2,7 5.5,10.5 12,4"/></svg>
</div>
</button>
</div>
<span class="ob-error" id="ob-topics-error">Please select at least one topic</span>
<div class="ob-field" style="margin-top: 24px;">
<label class="ob-label" for="ob-frequency">Email frequency</label>
<select id="ob-frequency" class="ob-input ob-select">
<option value="daily">Daily Digest</option>
<option value="weekly" selected>Weekly Summary</option>
<option value="important">Important Only</option>
</select>
</div>
<div class="ob-nav">
<button class="ob-btn ob-btn-outline" data-prev="1">Back</button>
<button class="ob-btn ob-btn-primary" data-next="3">Next</button>
</div>
</div>
<!-- Step 3: Invite Team -->
<div class="ob-panel" id="step-3">
<h2 class="ob-heading">Invite your team</h2>
<p class="ob-subtext">Collaborate better by inviting teammates. You can always do this later.</p>
<div class="ob-invite-input-row">
<input type="email" id="ob-invite-email" class="ob-input" placeholder="colleague@company.com" />
<button class="ob-btn ob-btn-primary ob-invite-add" id="ob-invite-add">Add</button>
</div>
<span class="ob-error" id="ob-email-error">Please enter a valid email</span>
<div class="ob-invite-list" id="ob-invite-list"></div>
<div class="ob-nav">
<button class="ob-btn ob-btn-outline" data-prev="2">Back</button>
<div class="ob-nav-right">
<button class="ob-skip" id="ob-skip-team">Skip this step</button>
<button class="ob-btn ob-btn-primary" data-next="4">Send Invites</button>
</div>
</div>
</div>
<!-- Step 4: Complete -->
<div class="ob-panel" id="step-4">
<div class="ob-confetti-area" id="ob-confetti-area"></div>
<div class="ob-complete-content">
<div class="ob-check-circle">
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" stroke="#fff" stroke-width="3.5">
<polyline points="14,24 22,32 34,16"/>
</svg>
</div>
<h2 class="ob-heading ob-heading-complete">You're all set!</h2>
<p class="ob-welcome-msg" id="ob-welcome-msg">Welcome to the platform!</p>
<div class="ob-summary" id="ob-summary"></div>
<button class="ob-btn ob-btn-cta" id="ob-go-dashboard">Go to Dashboard</button>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>Onboarding Page
A multi-step onboarding wizard for new users. Guides through profile setup, preferences, team invites, and a completion celebration screen.
Features
- Step indicator — numbered steps with progress bar (1. Profile, 2. Preferences, 3. Team, 4. Complete)
- Step 1: Profile — avatar upload, display name, role select, company name
- Step 2: Preferences — select interests/topics (card grid with checkboxes), email frequency select
- Step 3: Team — invite team members by email, skip option
- Step 4: Complete — confetti animation, welcome message, “Go to Dashboard” CTA
- Navigation — Back/Next buttons, skip link, validation per step
- Progress persistence — steps marked complete as user advances
When to use it
- SaaS product onboarding
- App first-run experience
- User setup wizard