<div><span class="lbl">Dots, the current step stretches into a pill</span>
<div class="wz wz-tour" data-wizard role="group" aria-labelledby="tour-title">
<h2 class="wz-title" id="tour-title">Welcome to Lumen Notes</h2>
<p class="wz-sub">Five things you'll use every day, in under a minute.</p>
<section class="wz-panel" data-step aria-labelledby="tour-h1">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13 3 5 14h6l-1 7 8-11h-6z"/></svg></span>
<h3 class="wz-h" id="tour-h1" tabindex="-1"><span class="wz-kick">Step 1 of 5</span>Capture from anywhere</h3>
<p class="wz-p">Press Ctrl + Shift + N in any app to open a quick note. It waits in your Inbox until you file it.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h2">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.5 7.5A1.5 1.5 0 0 1 5 6h4l2 2h8a1.5 1.5 0 0 1 1.5 1.5v8A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5z"/></svg></span>
<h3 class="wz-h" id="tour-h2" tabindex="-1"><span class="wz-kick">Step 2 of 5</span>Sort with notebooks and tags</h3>
<p class="wz-p">Notebooks hold projects, tags cut across them. A note can live in one notebook and carry any number of tags.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h3">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1"/><path d="M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1"/></svg></span>
<h3 class="wz-h" id="tour-h3" tabindex="-1"><span class="wz-kick">Step 3 of 5</span>Link ideas together</h3>
<p class="wz-p">Type two square brackets to link another note. Every note lists the notes that point back to it.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h4">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.2-4.2"/></svg></span>
<h3 class="wz-h" id="tour-h4" tabindex="-1"><span class="wz-kick">Step 4 of 5</span>Find it in a second</h3>
<p class="wz-p">Search reads your notes, PDFs and the text inside photos, so a snapped whiteboard turns up too.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h5">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 0 0-14.3-4.9L4 8"/><path d="M4 4v4h4"/><path d="M4 13a8 8 0 0 0 14.3 4.9L20 16"/><path d="M20 20v-4h-4"/></svg></span>
<h3 class="wz-h" id="tour-h5" tabindex="-1"><span class="wz-kick">Step 5 of 5</span>Pick up on any device</h3>
<p class="wz-p">Notes sync to your phone, laptop and the web, and stay editable offline. Changes merge when you reconnect.</p>
</section>
<section class="wz-panel wz-done" data-done hidden aria-labelledby="tour-h6">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h3 class="wz-h" id="tour-h6" tabindex="-1">You're all set</h3>
<p class="wz-p">Your first notebook, Ideas, is ready and waiting.</p>
<button type="button" class="wz-btn ghost" data-restart>Replay the tour</button>
</section>
<div class="wz-tour-nav">
<button type="button" class="wz-btn ghost" data-back hidden>Back</button>
<ol class="wz-dots" data-stepper aria-label="Progress">
<li><span class="sr-only">Capture</span><span class="sr-only" data-sr></span></li>
<li><span class="sr-only">Organise</span><span class="sr-only" data-sr></span></li>
<li><span class="sr-only">Link</span><span class="sr-only" data-sr></span></li>
<li><span class="sr-only">Search</span><span class="sr-only" data-sr></span></li>
<li><span class="sr-only">Sync</span><span class="sr-only" data-sr></span></li>
</ol>
<button type="button" class="wz-btn" data-next hidden>Next</button>
<button type="button" class="wz-btn" data-finish>Get started</button>
</div>
</div></div>
<div><span class="lbl">Icons with labels under them</span>
<div class="wz" data-wizard role="group" aria-labelledby="ord-title">
<h2 class="wz-title" id="ord-title">Order #LX-2291</h2>
<p class="wz-sub">Larkfield Home · 2 items · Tracked by Parcelway</p>
<ol class="wz-icons" data-stepper aria-label="Progress">
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3.5h6v3H9z"/><path d="M9 5H6.5A1.5 1.5 0 0 0 5 6.5v13A1.5 1.5 0 0 0 6.5 21h11a1.5 1.5 0 0 0 1.5-1.5v-13A1.5 1.5 0 0 0 17.5 5H15"/><path d="m9 13.5 2 2 4-4"/></svg></span><span class="wz-name">Placed</span><span class="sr-only" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 7.5 12 3 3.5 7.5v9L12 21l8.5-4.5z"/><path d="m3.5 7.5 8.5 4.5 8.5-4.5M12 12v9"/></svg></span><span class="wz-name">Packed</span><span class="sr-only" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 16.5v-10h11v10M13.5 9.5h4l3 3.5v3.5h-1.5M9 16.5h6"/><circle cx="6.5" cy="17" r="2"/><circle cx="17" cy="17" r="2"/></svg></span><span class="wz-name">Shipped</span><span class="sr-only" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.3"/></svg></span><span class="wz-name">Out for delivery</span><span class="sr-only" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 10.5 12 4l8 6.5V20H4z"/><path d="m9 14 2.2 2.2L15.5 12"/></svg></span><span class="wz-name">Delivered</span><span class="sr-only" data-sr></span></li>
</ol>
<section class="wz-panel" data-step aria-labelledby="ord-h1">
<h3 class="wz-h" id="ord-h1" tabindex="-1"><span class="wz-kick">Step 1 of 5</span>Order placed</h3>
<p class="wz-p">A Hekla stoneware mug set and an Oslo linen throw. Payment of €84.90 is confirmed.</p>
<p class="wz-meta">Mon 13 Oct, 18:40 · Online store</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h2">
<h3 class="wz-h" id="ord-h2" tabindex="-1"><span class="wz-kick">Step 2 of 5</span>Packed and labelled</h3>
<p class="wz-p">Both items are in one 2.1 kg parcel, waiting for the Parcelway collection.</p>
<p class="wz-meta">Tue 14 Oct, 09:12 · Rotterdam hub</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h3">
<h3 class="wz-h" id="ord-h3" tabindex="-1"><span class="wz-kick">Step 3 of 5</span>On its way</h3>
<p class="wz-p">Your parcel left the hub and is travelling to your local depot.</p>
<p class="wz-meta">Tue 14 Oct, 16:05 · Rotterdam hub</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h4">
<h3 class="wz-h" id="ord-h4" tabindex="-1"><span class="wz-kick">Step 4 of 5</span>Out for delivery</h3>
<p class="wz-p">Sanne is driving today. You are stop 14 of 22, expected between 12:30 and 14:30.</p>
<p class="wz-meta">Thu 16 Oct, 07:30 · Utrecht depot</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h5">
<h3 class="wz-h" id="ord-h5" tabindex="-1"><span class="wz-kick">Step 5 of 5</span>Delivered</h3>
<p class="wz-p">Left with your neighbour at number 16 and signed for by M. de Vries.</p>
<p class="wz-meta">Thu 16 Oct, 13:48 · Utrecht</p>
</section>
<div class="wz-nav">
<button type="button" class="wz-btn ghost" data-back hidden>Back</button>
<button type="button" class="wz-btn" data-next hidden>Next</button>
</div>
</div></div>
<div><span class="lbl">Progress line with markers</span>
<div class="wz" data-wizard role="group" aria-labelledby="mtg-title">
<h2 class="wz-title" id="mtg-title">Mortgage pre-approval</h2>
<p class="wz-sub">Halden Bank · Application HB-40718</p>
<ol class="wz-line" data-stepper aria-label="Progress">
<li><span class="wz-mark"></span>Income<span class="sr-only" data-sr></span></li>
<li><span class="wz-mark"></span>Assets<span class="sr-only" data-sr></span></li>
<li><span class="wz-mark"></span>Credit check<span class="sr-only" data-sr></span></li>
<li><span class="wz-mark"></span>Offer<span class="sr-only" data-sr></span></li>
</ol>
<section class="wz-panel" data-step aria-labelledby="mtg-h1">
<h3 class="wz-h" id="mtg-h1" tabindex="-1"><span class="wz-kick">Step 1 of 4</span>Your income is verified</h3>
<p class="wz-p">We matched your last three payslips with your bank statements.</p>
<dl class="wz-facts"><div><dt>Annual income</dt><dd>£64,200</dd></div><div><dt>Employer</dt><dd>Merrow Health Trust</dd></div></dl>
</section>
<section class="wz-panel" data-step aria-labelledby="mtg-h2">
<h3 class="wz-h" id="mtg-h2" tabindex="-1"><span class="wz-kick">Step 2 of 4</span>Your deposit is confirmed</h3>
<p class="wz-p">Savings held for more than three months count toward the deposit.</p>
<dl class="wz-facts"><div><dt>Deposit</dt><dd>£48,000</dd></div><div><dt>Held in</dt><dd>Savings and ISA</dd></div></dl>
</section>
<section class="wz-panel" data-step aria-labelledby="mtg-h3">
<h3 class="wz-h" id="mtg-h3" tabindex="-1"><span class="wz-kick">Step 3 of 4</span>Credit check passed</h3>
<p class="wz-p">A soft search: other lenders can't see it and it doesn't affect your score.</p>
<dl class="wz-facts"><div><dt>Result</dt><dd>Excellent</dd></div><div><dt>Checked</dt><dd>Wed 22 Oct</dd></div></dl>
</section>
<section class="wz-panel" data-step aria-labelledby="mtg-h4">
<h3 class="wz-h" id="mtg-h4" tabindex="-1"><span class="wz-kick">Step 4 of 4</span>You can borrow up to £285,000</h3>
<p class="wz-p">This agreement in principle is valid for 90 days. It is not a final mortgage offer.</p>
<dl class="wz-facts"><div><dt>Rate</dt><dd>4.19% fixed, 5 years</dd></div><div><dt>Monthly</dt><dd>£1,392 over 30 years</dd></div></dl>
</section>
<section class="wz-panel wz-done" data-done hidden aria-labelledby="mtg-h5">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h3 class="wz-h" id="mtg-h5" tabindex="-1">Offer accepted</h3>
<p class="wz-p">A copy of agreement HB-40718 is in your inbox. An adviser will call within two working days.</p>
<button type="button" class="wz-btn ghost" data-restart>Start over</button>
</section>
<div class="wz-nav" data-nav>
<button type="button" class="wz-btn ghost" data-back hidden>Back</button>
<button type="button" class="wz-btn" data-next hidden>Next</button>
<button type="button" class="wz-btn" data-finish>Accept offer</button>
</div>
</div></div>
<div><span class="lbl">Mobile header with a progress bar</span>
<div class="wz wz-app" data-wizard role="group" aria-labelledby="bank-title">
<h2 class="sr-only" id="bank-title">Add a bank account</h2>
<div class="wz-appbar">
<button type="button" class="wz-iconbtn" data-back hidden aria-label="Back"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 18l-6-6 6-6"/></svg></button>
<p class="wz-apptitle" aria-hidden="true"><span data-count>Step 1 of 4</span><b data-name>Choose bank</b></p>
</div>
<div class="wz-meter" aria-hidden="true"><span></span></div>
<ol class="sr-only" data-stepper aria-label="Progress">
<li>Choose bank<span data-sr></span></li>
<li>Sign in<span data-sr></span></li>
<li>Pick account<span data-sr></span></li>
<li>Confirm<span data-sr></span></li>
</ol>
<section class="wz-panel" data-step aria-labelledby="bank-h1">
<h3 class="wz-h" id="bank-h1" tabindex="-1"><span class="sr-only">Step 1 of 4: </span>Choose your bank</h3>
<p class="wz-p">Pennant pays your invoices out to this account. We connect to 40 UK banks.</p>
<p class="wz-meta">Selected: Fernbank</p>
</section>
<section class="wz-panel" data-step aria-labelledby="bank-h2">
<h3 class="wz-h" id="bank-h2" tabindex="-1"><span class="sr-only">Step 2 of 4: </span>Sign in to Fernbank</h3>
<p class="wz-p">You approve the link in the Fernbank app. Pennant never sees your password.</p>
<p class="wz-meta">Secure Open Banking connection</p>
</section>
<section class="wz-panel" data-step aria-labelledby="bank-h3">
<h3 class="wz-h" id="bank-h3" tabindex="-1"><span class="sr-only">Step 3 of 4: </span>Pick an account</h3>
<p class="wz-p">We found two accounts. Payouts will go to the one you choose here.</p>
<p class="wz-meta">Everyday account ending 4821</p>
</section>
<section class="wz-panel" data-step aria-labelledby="bank-h4">
<h3 class="wz-h" id="bank-h4" tabindex="-1"><span class="sr-only">Step 4 of 4: </span>Confirm and link</h3>
<p class="wz-p">Payouts arrive the next working day. You can change the account in Settings.</p>
<p class="wz-meta">Fernbank Everyday ending 4821</p>
</section>
<section class="wz-panel wz-done" data-done hidden aria-labelledby="bank-h5">
<span class="wz-badge"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h3 class="wz-h" id="bank-h5" tabindex="-1">Account linked</h3>
<p class="wz-p">Your next payout, £1,240.00, goes to Everyday ending 4821 on Friday.</p>
<button type="button" class="wz-btn ghost" data-restart>Add another account</button>
</section>
<div class="wz-appfoot" data-nav>
<button type="button" class="wz-btn" data-next hidden>Continue</button>
<button type="button" class="wz-btn" data-finish>Link account</button>
</div>
</div></div>
CSS
/* Stepper styles: four compact wizards that share one script. Each has an <ol data-stepper> (dots, icons,
a progress line, or a visually hidden list behind an app header), one <section data-step> per step and
Back / Next buttons. The script sets data-state, aria-current="step" and the --wz-i / --wz-n / --wz-p
custom properties the progress styles read, and moves focus to the new step's heading. */
.wz{--wz-accent:#0369a1;--wz-accent-dark:#075985;--wz-soft:#e0f2fe;--wz-done:#0f766e;--wz-done-soft:#ccfbf1;--wz-ink:#0c1a26;--wz-muted:#4a5b6b;--wz-line:#7d8d9d;--wz-track:#dbe4ec;
background:#fff;border-radius:18px;padding:28px 30px;box-shadow:0 1px 2px rgba(12,26,38,.06),0 10px 30px -12px rgba(7,89,133,.22);color:var(--wz-ink)}
.wz [hidden]{display:none!important}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.wz-title{font-size:1.2rem;line-height:1.35;font-weight:700;letter-spacing:-.015em;margin:0}
.wz-sub{margin:2px 0 0;color:var(--wz-muted);font-size:.875rem}
/* step panels */
.wz-panel{display:grid;gap:8px;margin-top:24px;animation:wz-in .3s ease}
@keyframes wz-in{from{opacity:0;transform:translateY(6px)}}
.wz-h{margin:0;font-size:1.1rem;line-height:1.35;font-weight:700;letter-spacing:-.01em}
.wz-h:focus{outline:none}
.wz-kick{display:block;font-size:.7rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--wz-accent-dark);margin-bottom:2px}
.wz-p{margin:0;color:var(--wz-muted);font-size:.92rem}
.wz-meta{justify-self:start;margin:4px 0 0;padding:5px 10px;border-radius:8px;background:#f0f5f9;font-size:.82rem;font-weight:600;font-variant-numeric:tabular-nums}
.wz-badge{width:48px;height:48px;border-radius:14px;display:grid;place-items:center;background:var(--wz-soft);color:var(--wz-accent-dark);margin-bottom:6px}
.wz-badge svg,.wz-ico svg,.wz-iconbtn svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.wz-done .wz-badge{background:var(--wz-done-soft);color:var(--wz-done)}
.wz-facts{margin:6px 0 0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.wz-facts div{background:#f0f5f9;border-radius:10px;padding:10px 12px}
.wz-facts dt{font-size:.75rem;color:var(--wz-muted)}
.wz-facts dd{margin:0;font-weight:700;font-size:.95rem}
/* buttons */
.wz-nav{display:flex;gap:10px;margin-top:24px;padding-top:18px;border-top:1px solid #e3eaf0}
.wz-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:0 20px;border-radius:10px;border:1.5px solid var(--wz-accent);background:var(--wz-accent);color:#fff;font:inherit;font-size:.95rem;font-weight:600;cursor:pointer;transition:background .2s}
.wz-btn:hover{background:var(--wz-accent-dark)}
.wz-btn:focus-visible,.wz-iconbtn:focus-visible{outline:none;box-shadow:0 0 0 3px #fff,0 0 0 5px var(--wz-accent)}
.wz-btn.ghost{background:#fff;color:var(--wz-accent-dark);border-color:var(--wz-line)}
.wz-btn.ghost:hover{background:#f0f9ff}
.wz-nav .wz-btn:not(.ghost){margin-left:auto}
/* A. dots: the current dot stretches into a pill, done dots turn teal */
.wz-tour .wz-panel{min-height:170px;align-content:start}
.wz-tour-nav{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin-top:20px}
.wz-tour-nav [data-back]{grid-column:1;justify-self:start}
.wz-tour-nav [data-next],.wz-tour-nav [data-finish]{grid-column:3;grid-row:1;justify-self:end}
.wz-dots{grid-column:2;grid-row:1;list-style:none;margin:0;padding:0;display:flex;align-items:center;gap:7px}
.wz-dots li{width:9px;height:9px;border-radius:999px;background:var(--wz-line);transition:width .3s ease,background-color .3s}
.wz-dots [data-state="done"]{background:var(--wz-done)}
.wz-dots [data-state="current"]{width:28px;background:var(--wz-accent)}
/* B. icons in circles, joined by connectors, labels under them */
.wz-icons{list-style:none;margin:24px 0 0;padding:0;display:grid;grid-template-columns:repeat(5,1fr)}
.wz-icons li{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;font-size:.78rem;line-height:1.3;color:var(--wz-muted)}
.wz-icons li + li::before{content:"";position:absolute;top:19px;left:calc(-50% + 26px);right:calc(50% + 26px);height:2px;border-radius:2px;background:var(--wz-line);transition:background .25s}
.wz-ico{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;border:2px solid var(--wz-line);background:#fff;color:#5a6a7a;transition:background .25s,border-color .25s,box-shadow .25s}
.wz-ico svg{width:20px;height:20px;stroke-width:1.8}
.wz-icons [data-state="done"] .wz-ico{background:var(--wz-done);border-color:var(--wz-done);color:#fff}
.wz-icons [data-state="current"] .wz-ico{background:var(--wz-accent);border-color:var(--wz-accent);color:#fff;box-shadow:0 0 0 5px var(--wz-soft)}
.wz-icons [data-state="current"]{color:var(--wz-ink);font-weight:600}
.wz-icons [data-state="done"] + li::before,.wz-icons [data-state="current"]::before{background:var(--wz-done)}
@media (max-width:520px){.wz-ico{width:34px;height:34px}.wz-ico svg{width:17px;height:17px}
.wz-icons li + li::before{top:16px;left:calc(-50% + 22px);right:calc(50% + 22px)}
.wz-icons [data-state]:not([data-state="current"]) .wz-name{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}}
/* C. a thin track that fills to the current marker (width from --wz-p, set by the script) */
.wz-line{position:relative;list-style:none;margin:26px 0 0;padding:0;display:grid;grid-template-columns:repeat(var(--wz-n,4),1fr)}
.wz-line::before,.wz-line::after{content:"";position:absolute;top:6px;left:calc(50% / var(--wz-n,4));height:4px;border-radius:4px}
.wz-line::before{right:calc(50% / var(--wz-n,4));background:var(--wz-track)}
.wz-line::after{width:calc((100% - 100% / var(--wz-n,4)) * var(--wz-p,0));background:var(--wz-done);transition:width .4s ease}
.wz-line li{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;font-size:.8rem;line-height:1.3;color:var(--wz-muted)}
.wz-mark{width:16px;height:16px;border-radius:50%;border:2px solid var(--wz-line);background:#fff;transition:border-color .25s,background .25s,box-shadow .25s}
.wz-line [data-state="done"] .wz-mark{background:var(--wz-done);border-color:var(--wz-done)}
.wz-line [data-state="current"] .wz-mark{border:4px solid var(--wz-accent);box-shadow:0 0 0 4px var(--wz-soft)}
.wz-line [data-state="current"]{color:var(--wz-ink);font-weight:600}
/* D. app header: back arrow, "Step 2 of 4" over the step name and a thin bar; the real stepper is visually hidden */
.wz-app{max-width:400px;width:100%;margin:0 auto;padding:0;overflow:hidden}
.wz-appbar{display:grid;grid-template-columns:44px 1fr 44px;align-items:center;gap:6px;padding:10px 10px 12px}
.wz-appbar [data-back]{grid-column:1;grid-row:1}
.wz-iconbtn{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;border:0;background:transparent;color:var(--wz-ink);cursor:pointer;transition:background .2s}
.wz-iconbtn:hover{background:#f0f5f9}
.wz-apptitle{grid-column:2;grid-row:1;margin:0;display:grid;text-align:center;line-height:1.3}
.wz-apptitle span{font-size:.75rem;color:var(--wz-muted)}
.wz-apptitle b{font-size:1rem}
.wz-meter{height:4px;background:var(--wz-track)}
.wz-meter span{display:block;height:100%;width:calc((var(--wz-i,0) + 1) / var(--wz-n,4) * 100%);background:var(--wz-accent);border-radius:0 4px 4px 0;transition:width .4s ease}
.wz-app .wz-panel{margin:0;padding:24px 22px 4px}
.wz-app .wz-done{padding-bottom:24px}
.wz-appfoot{padding:20px 22px 22px}
.wz-appfoot .wz-btn{width:100%}
.wz-done{justify-items:center;text-align:center}
.wz-done .wz-btn{margin-top:10px}
@media (prefers-reduced-motion:reduce){.wz *,.wz *::before,.wz *::after,.wz ::before,.wz ::after{transition:none!important;animation:none!important}}
@media (max-width:600px){.wz{padding:24px 18px}.wz-app{padding:0}.wz-app .wz-panel{padding:22px 18px 4px}.wz-appfoot{padding:18px 18px 20px}}
JavaScript
// Stepper wizard: shows one step at a time, marks the stepper with data-state and aria-current="step",
// sets --wz-i / --wz-n / --wz-p (0 to 1) for progress styles and moves focus to the new step's heading.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li'), n = panels.length;
var back = wz.querySelector('[data-back]'), next = wz.querySelector('[data-next]'), finish = wz.querySelector('[data-finish]');
var done = wz.querySelector('[data-done]'), navs = wz.querySelectorAll('[data-nav]'), at = 0;
var count = wz.querySelector('[data-count]'), name = wz.querySelector('[data-name]');
function mark(i) {
var k = Math.min(i, n - 1);
steps.forEach(function (s, j) {
s.dataset.state = j < i ? 'done' : j === i ? 'current' : 'next';
if (j === i) s.setAttribute('aria-current', 'step'); else s.removeAttribute('aria-current');
s.querySelector('[data-sr]').textContent = j < i ? ', completed' : '';
});
wz.style.setProperty('--wz-i', k); wz.style.setProperty('--wz-n', n); wz.style.setProperty('--wz-p', n > 1 ? k / (n - 1) : 1);
if (count) count.textContent = 'Step ' + (k + 1) + ' of ' + n;
if (name) name.textContent = steps[k].firstChild.textContent.trim(); // the li's leading text is the step name
}
function go(i, focus) {
at = i; mark(i);
panels.forEach(function (p, j) { p.hidden = j !== i; });
if (back) back.hidden = i === 0;
next.hidden = i === n - 1;
if (finish) finish.hidden = !next.hidden;
if (focus) panels[i].querySelector('[tabindex="-1"]').focus();
}
if (back) back.addEventListener('click', function () { go(at - 1, true); });
next.addEventListener('click', function () { go(at + 1, true); });
// Last step: do the real thing here (navigate, save). The demo shows the done panel instead.
if (finish && done) finish.addEventListener('click', function () {
panels[at].hidden = true; finish.hidden = true; if (back) back.hidden = true; mark(n);
navs.forEach(function (x) { x.hidden = true; });
done.hidden = false; done.querySelector('[tabindex="-1"]').focus();
});
if (done) done.querySelector('[data-restart]').addEventListener('click', function () {
done.hidden = true; navs.forEach(function (x) { x.hidden = false; }); go(0, true);
});
go(0);
});
Bootstrap 5 code
Requires: Bootstrap 5.3.8 CSS
HTML
<div><span class="lbl">Dots, the current step stretches into a pill</span>
<div class="wz wz-tour card" data-wizard role="group" aria-labelledby="tour-title"><div class="card-body">
<h2 class="wz-title fw-bold mb-0" id="tour-title">Welcome to Lumen Notes</h2>
<p class="text-body-secondary mt-1 mb-0 small">Five things you'll use every day, in under a minute.</p>
<section class="wz-panel" data-step aria-labelledby="tour-h1">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M13 3 5 14h6l-1 7 8-11h-6z"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="tour-h1" tabindex="-1"><span class="wz-kick">Step 1 of 5</span>Capture from anywhere</h3>
<p class="wz-p text-body-secondary mb-0">Press Ctrl + Shift + N in any app to open a quick note. It waits in your Inbox until you file it.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h2">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3.5 7.5A1.5 1.5 0 0 1 5 6h4l2 2h8a1.5 1.5 0 0 1 1.5 1.5v8A1.5 1.5 0 0 1 19 19H5a1.5 1.5 0 0 1-1.5-1.5z"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="tour-h2" tabindex="-1"><span class="wz-kick">Step 2 of 5</span>Sort with notebooks and tags</h3>
<p class="wz-p text-body-secondary mb-0">Notebooks hold projects, tags cut across them. A note can live in one notebook and carry any number of tags.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h3">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 14a4 4 0 0 0 5.66 0l3-3a4 4 0 0 0-5.66-5.66l-1 1"/><path d="M14 10a4 4 0 0 0-5.66 0l-3 3a4 4 0 0 0 5.66 5.66l1-1"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="tour-h3" tabindex="-1"><span class="wz-kick">Step 3 of 5</span>Link ideas together</h3>
<p class="wz-p text-body-secondary mb-0">Type two square brackets to link another note. Every note lists the notes that point back to it.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h4">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="6"/><path d="m20 20-4.2-4.2"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="tour-h4" tabindex="-1"><span class="wz-kick">Step 4 of 5</span>Find it in a second</h3>
<p class="wz-p text-body-secondary mb-0">Search reads your notes, PDFs and the text inside photos, so a snapped whiteboard turns up too.</p>
</section>
<section class="wz-panel" data-step aria-labelledby="tour-h5">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 0 0-14.3-4.9L4 8"/><path d="M4 4v4h4"/><path d="M4 13a8 8 0 0 0 14.3 4.9L20 16"/><path d="M20 20v-4h-4"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="tour-h5" tabindex="-1"><span class="wz-kick">Step 5 of 5</span>Pick up on any device</h3>
<p class="wz-p text-body-secondary mb-0">Notes sync to your phone, laptop and the web, and stay editable offline. Changes merge when you reconnect.</p>
</section>
<section class="wz-panel wz-done" data-done hidden aria-labelledby="tour-h6">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="tour-h6" tabindex="-1">You're all set</h3>
<p class="wz-p text-body-secondary mb-0">Your first notebook, Ideas, is ready and waiting.</p>
<button type="button" class="btn btn-outline-secondary" data-restart>Replay the tour</button>
</section>
<div class="wz-tour-nav">
<button type="button" class="btn btn-outline-secondary" data-back hidden>Back</button>
<ol class="wz-dots" data-stepper aria-label="Progress">
<li><span class="visually-hidden">Capture</span><span class="visually-hidden" data-sr></span></li>
<li><span class="visually-hidden">Organise</span><span class="visually-hidden" data-sr></span></li>
<li><span class="visually-hidden">Link</span><span class="visually-hidden" data-sr></span></li>
<li><span class="visually-hidden">Search</span><span class="visually-hidden" data-sr></span></li>
<li><span class="visually-hidden">Sync</span><span class="visually-hidden" data-sr></span></li>
</ol>
<button type="button" class="btn btn-primary" data-next hidden>Next</button>
<button type="button" class="btn btn-primary" data-finish>Get started</button>
</div>
</div></div></div>
<div><span class="lbl">Icons with labels under them</span>
<div class="wz card" data-wizard role="group" aria-labelledby="ord-title"><div class="card-body">
<h2 class="wz-title fw-bold mb-0" id="ord-title">Order #LX-2291</h2>
<p class="text-body-secondary mt-1 mb-0 small">Larkfield Home · 2 items · Tracked by Parcelway</p>
<ol class="wz-icons" data-stepper aria-label="Progress">
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3.5h6v3H9z"/><path d="M9 5H6.5A1.5 1.5 0 0 0 5 6.5v13A1.5 1.5 0 0 0 6.5 21h11a1.5 1.5 0 0 0 1.5-1.5v-13A1.5 1.5 0 0 0 17.5 5H15"/><path d="m9 13.5 2 2 4-4"/></svg></span><span class="wz-name">Placed</span><span class="visually-hidden" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20.5 7.5 12 3 3.5 7.5v9L12 21l8.5-4.5z"/><path d="m3.5 7.5 8.5 4.5 8.5-4.5M12 12v9"/></svg></span><span class="wz-name">Packed</span><span class="visually-hidden" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2.5 16.5v-10h11v10M13.5 9.5h4l3 3.5v3.5h-1.5M9 16.5h6"/><circle cx="6.5" cy="17" r="2"/><circle cx="17" cy="17" r="2"/></svg></span><span class="wz-name">Shipped</span><span class="visually-hidden" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.3"/></svg></span><span class="wz-name">Out for delivery</span><span class="visually-hidden" data-sr></span></li>
<li><span class="wz-ico"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 10.5 12 4l8 6.5V20H4z"/><path d="m9 14 2.2 2.2L15.5 12"/></svg></span><span class="wz-name">Delivered</span><span class="visually-hidden" data-sr></span></li>
</ol>
<section class="wz-panel" data-step aria-labelledby="ord-h1">
<h3 class="wz-h fw-bold mb-0" id="ord-h1" tabindex="-1"><span class="wz-kick">Step 1 of 5</span>Order placed</h3>
<p class="wz-p text-body-secondary mb-0">A Hekla stoneware mug set and an Oslo linen throw. Payment of €84.90 is confirmed.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Mon 13 Oct, 18:40 · Online store</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h2">
<h3 class="wz-h fw-bold mb-0" id="ord-h2" tabindex="-1"><span class="wz-kick">Step 2 of 5</span>Packed and labelled</h3>
<p class="wz-p text-body-secondary mb-0">Both items are in one 2.1 kg parcel, waiting for the Parcelway collection.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Tue 14 Oct, 09:12 · Rotterdam hub</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h3">
<h3 class="wz-h fw-bold mb-0" id="ord-h3" tabindex="-1"><span class="wz-kick">Step 3 of 5</span>On its way</h3>
<p class="wz-p text-body-secondary mb-0">Your parcel left the hub and is travelling to your local depot.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Tue 14 Oct, 16:05 · Rotterdam hub</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h4">
<h3 class="wz-h fw-bold mb-0" id="ord-h4" tabindex="-1"><span class="wz-kick">Step 4 of 5</span>Out for delivery</h3>
<p class="wz-p text-body-secondary mb-0">Sanne is driving today. You are stop 14 of 22, expected between 12:30 and 14:30.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Thu 16 Oct, 07:30 · Utrecht depot</p>
</section>
<section class="wz-panel" data-step aria-labelledby="ord-h5">
<h3 class="wz-h fw-bold mb-0" id="ord-h5" tabindex="-1"><span class="wz-kick">Step 5 of 5</span>Delivered</h3>
<p class="wz-p text-body-secondary mb-0">Left with your neighbour at number 16 and signed for by M. de Vries.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Thu 16 Oct, 13:48 · Utrecht</p>
</section>
<div class="wz-nav">
<button type="button" class="btn btn-outline-secondary" data-back hidden>Back</button>
<button type="button" class="btn btn-primary" data-next hidden>Next</button>
</div>
</div></div></div>
<div><span class="lbl">Progress line with markers</span>
<div class="wz card" data-wizard role="group" aria-labelledby="mtg-title"><div class="card-body">
<h2 class="wz-title fw-bold mb-0" id="mtg-title">Mortgage pre-approval</h2>
<p class="text-body-secondary mt-1 mb-0 small">Halden Bank · Application HB-40718</p>
<ol class="wz-line" data-stepper aria-label="Progress">
<li><span class="wz-mark"></span>Income<span class="visually-hidden" data-sr></span></li>
<li><span class="wz-mark"></span>Assets<span class="visually-hidden" data-sr></span></li>
<li><span class="wz-mark"></span>Credit check<span class="visually-hidden" data-sr></span></li>
<li><span class="wz-mark"></span>Offer<span class="visually-hidden" data-sr></span></li>
</ol>
<section class="wz-panel" data-step aria-labelledby="mtg-h1">
<h3 class="wz-h fw-bold mb-0" id="mtg-h1" tabindex="-1"><span class="wz-kick">Step 1 of 4</span>Your income is verified</h3>
<p class="wz-p text-body-secondary mb-0">We matched your last three payslips with your bank statements.</p>
<dl class="wz-facts"><div><dt>Annual income</dt><dd>£64,200</dd></div><div><dt>Employer</dt><dd>Merrow Health Trust</dd></div></dl>
</section>
<section class="wz-panel" data-step aria-labelledby="mtg-h2">
<h3 class="wz-h fw-bold mb-0" id="mtg-h2" tabindex="-1"><span class="wz-kick">Step 2 of 4</span>Your deposit is confirmed</h3>
<p class="wz-p text-body-secondary mb-0">Savings held for more than three months count toward the deposit.</p>
<dl class="wz-facts"><div><dt>Deposit</dt><dd>£48,000</dd></div><div><dt>Held in</dt><dd>Savings and ISA</dd></div></dl>
</section>
<section class="wz-panel" data-step aria-labelledby="mtg-h3">
<h3 class="wz-h fw-bold mb-0" id="mtg-h3" tabindex="-1"><span class="wz-kick">Step 3 of 4</span>Credit check passed</h3>
<p class="wz-p text-body-secondary mb-0">A soft search: other lenders can't see it and it doesn't affect your score.</p>
<dl class="wz-facts"><div><dt>Result</dt><dd>Excellent</dd></div><div><dt>Checked</dt><dd>Wed 22 Oct</dd></div></dl>
</section>
<section class="wz-panel" data-step aria-labelledby="mtg-h4">
<h3 class="wz-h fw-bold mb-0" id="mtg-h4" tabindex="-1"><span class="wz-kick">Step 4 of 4</span>You can borrow up to £285,000</h3>
<p class="wz-p text-body-secondary mb-0">This agreement in principle is valid for 90 days. It is not a final mortgage offer.</p>
<dl class="wz-facts"><div><dt>Rate</dt><dd>4.19% fixed, 5 years</dd></div><div><dt>Monthly</dt><dd>£1,392 over 30 years</dd></div></dl>
</section>
<section class="wz-panel wz-done" data-done hidden aria-labelledby="mtg-h5">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="mtg-h5" tabindex="-1">Offer accepted</h3>
<p class="wz-p text-body-secondary mb-0">A copy of agreement HB-40718 is in your inbox. An adviser will call within two working days.</p>
<button type="button" class="btn btn-outline-secondary" data-restart>Start over</button>
</section>
<div class="wz-nav" data-nav>
<button type="button" class="btn btn-outline-secondary" data-back hidden>Back</button>
<button type="button" class="btn btn-primary" data-next hidden>Next</button>
<button type="button" class="btn btn-primary" data-finish>Accept offer</button>
</div>
</div></div></div>
<div><span class="lbl">Mobile header with a progress bar</span>
<div class="wz wz-app card" data-wizard role="group" aria-labelledby="bank-title">
<h2 class="visually-hidden" id="bank-title">Add a bank account</h2>
<div class="wz-appbar">
<button type="button" class="btn wz-iconbtn" data-back hidden aria-label="Back"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19 12H5M11 18l-6-6 6-6"/></svg></button>
<p class="wz-apptitle" aria-hidden="true"><span data-count>Step 1 of 4</span><b data-name>Choose bank</b></p>
</div>
<div class="progress wz-meter" aria-hidden="true"><div class="progress-bar"></div></div>
<ol class="visually-hidden" data-stepper aria-label="Progress">
<li>Choose bank<span data-sr></span></li>
<li>Sign in<span data-sr></span></li>
<li>Pick account<span data-sr></span></li>
<li>Confirm<span data-sr></span></li>
</ol>
<section class="wz-panel" data-step aria-labelledby="bank-h1">
<h3 class="wz-h fw-bold mb-0" id="bank-h1" tabindex="-1"><span class="visually-hidden">Step 1 of 4: </span>Choose your bank</h3>
<p class="wz-p text-body-secondary mb-0">Pennant pays your invoices out to this account. We connect to 40 UK banks.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Selected: Fernbank</p>
</section>
<section class="wz-panel" data-step aria-labelledby="bank-h2">
<h3 class="wz-h fw-bold mb-0" id="bank-h2" tabindex="-1"><span class="visually-hidden">Step 2 of 4: </span>Sign in to Fernbank</h3>
<p class="wz-p text-body-secondary mb-0">You approve the link in the Fernbank app. Pennant never sees your password.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Secure Open Banking connection</p>
</section>
<section class="wz-panel" data-step aria-labelledby="bank-h3">
<h3 class="wz-h fw-bold mb-0" id="bank-h3" tabindex="-1"><span class="visually-hidden">Step 3 of 4: </span>Pick an account</h3>
<p class="wz-p text-body-secondary mb-0">We found two accounts. Payouts will go to the one you choose here.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Everyday account ending 4821</p>
</section>
<section class="wz-panel" data-step aria-labelledby="bank-h4">
<h3 class="wz-h fw-bold mb-0" id="bank-h4" tabindex="-1"><span class="visually-hidden">Step 4 of 4: </span>Confirm and link</h3>
<p class="wz-p text-body-secondary mb-0">Payouts arrive the next working day. You can change the account in Settings.</p>
<p class="wz-meta fw-semibold rounded-2 mb-0">Fernbank Everyday ending 4821</p>
</section>
<section class="wz-panel wz-done" data-done hidden aria-labelledby="bank-h5">
<span class="wz-badge rounded-4"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h3 class="wz-h fw-bold mb-0" id="bank-h5" tabindex="-1">Account linked</h3>
<p class="wz-p text-body-secondary mb-0">Your next payout, £1,240.00, goes to Everyday ending 4821 on Friday.</p>
<button type="button" class="btn btn-outline-secondary" data-restart>Add another account</button>
</section>
<div class="wz-appfoot" data-nav>
<button type="button" class="btn btn-primary" data-next hidden>Continue</button>
<button type="button" class="btn btn-primary" data-finish>Link account</button>
</div>
</div></div>
CSS
/* Stepper styles on Bootstrap 5: .card, .btn, .progress and .visually-hidden, restyled through Bootstrap's CSS
variables; the four stepper designs are the custom part. The script sets data-state, aria-current="step" and the
--wz-i / --wz-n / --wz-p custom properties the progress styles read, and moves focus to the new step's heading. */
.wz{--wz-accent:#0369a1;--wz-accent-dark:#075985;--wz-soft:#e0f2fe;--wz-done:#0f766e;--wz-done-soft:#ccfbf1;--wz-muted:#4a5b6b;--wz-line:#7d8d9d;--wz-track:#dbe4ec;
--bs-body-color:#0c1a26;--bs-secondary-color:#4a5b6b;--bs-card-color:#0c1a26;
border:0;border-radius:18px;box-shadow:0 1px 2px rgba(12,26,38,.06),0 10px 30px -12px rgba(7,89,133,.22);color:#0c1a26}
.wz .card-body{padding:28px 30px}
.wz-title{font-size:1.2rem;line-height:1.35;letter-spacing:-.015em}
.wz .btn{--bs-btn-padding-x:20px;--bs-btn-font-weight:600;--bs-btn-border-width:1.5px;--bs-btn-border-radius:10px;min-height:44px}
.wz .btn-primary{--bs-btn-bg:var(--wz-accent);--bs-btn-border-color:var(--wz-accent);--bs-btn-hover-bg:var(--wz-accent-dark);--bs-btn-hover-border-color:var(--wz-accent-dark);--bs-btn-active-bg:var(--wz-accent-dark);--bs-btn-active-border-color:var(--wz-accent-dark)}
.wz .btn-outline-secondary{--bs-btn-color:var(--wz-accent-dark);--bs-btn-border-color:var(--wz-line);--bs-btn-bg:#fff;--bs-btn-hover-bg:#f0f9ff;--bs-btn-hover-color:var(--wz-accent-dark);--bs-btn-hover-border-color:var(--wz-line);--bs-btn-active-bg:#e0f2fe;--bs-btn-active-color:var(--wz-accent-dark)}
.wz .btn:focus-visible{outline:0;box-shadow:0 0 0 3px #fff,0 0 0 5px var(--wz-accent)}
/* step panels */
.wz-panel{display:grid;gap:8px;margin-top:24px;animation:wz-in .3s ease}
@keyframes wz-in{from{opacity:0;transform:translateY(6px)}}
.wz-h{font-size:1.1rem;line-height:1.35;letter-spacing:-.01em}
.wz-h:focus{outline:none}
.wz-kick{display:block;font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--wz-accent-dark);margin-bottom:2px}
.wz-p{font-size:.92rem}
.wz-meta{justify-self:start;margin-top:4px;padding:5px 10px;font-size:.82rem;background:#f0f5f9;font-variant-numeric:tabular-nums}
.wz-badge{width:48px;height:48px;display:grid;place-items:center;background:var(--wz-soft);color:var(--wz-accent-dark);margin-bottom:6px}
.wz-badge svg,.wz-ico svg,.wz-iconbtn svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.wz-done .wz-badge{background:var(--wz-done-soft);color:var(--wz-done)}
.wz-facts{margin:6px 0 0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.wz-facts div{background:#f0f5f9;border-radius:10px;padding:10px 12px}
.wz-facts dt{font-size:.75rem;font-weight:400;color:var(--wz-muted)}
.wz-facts dd{margin:0;font-weight:700;font-size:.95rem}
.wz-nav{display:flex;gap:10px;margin-top:24px;padding-top:18px;border-top:1px solid #e3eaf0}
.wz-nav .btn-primary{margin-left:auto}
/* A. dots: the current dot stretches into a pill, done dots turn teal */
.wz-tour .wz-panel{min-height:170px;align-content:start}
.wz-tour-nav{display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin-top:20px}
.wz-tour-nav [data-back]{grid-column:1;justify-self:start}
.wz-tour-nav [data-next],.wz-tour-nav [data-finish]{grid-column:3;grid-row:1;justify-self:end}
.wz-dots{grid-column:2;grid-row:1;list-style:none;margin:0;padding:0;display:flex;align-items:center;gap:7px}
.wz-dots li{width:9px;height:9px;border-radius:999px;background:var(--wz-line);transition:width .3s ease,background-color .3s}
.wz-dots [data-state="done"]{background:var(--wz-done)}
.wz-dots [data-state="current"]{width:28px;background:var(--wz-accent)}
/* B. icons in circles, joined by connectors, labels under them */
.wz-icons{list-style:none;margin:24px 0 0;padding:0;display:grid;grid-template-columns:repeat(5,1fr)}
.wz-icons li{position:relative;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;font-size:.78rem;line-height:1.3;color:var(--wz-muted)}
.wz-icons li + li::before{content:"";position:absolute;top:19px;left:calc(-50% + 26px);right:calc(50% + 26px);height:2px;border-radius:2px;background:var(--wz-line);transition:background .25s}
.wz-ico{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;border:2px solid var(--wz-line);background:#fff;color:#5a6a7a;transition:background .25s,border-color .25s,box-shadow .25s}
.wz-ico svg{width:20px;height:20px;stroke-width:1.8}
.wz-icons [data-state="done"] .wz-ico{background:var(--wz-done);border-color:var(--wz-done);color:#fff}
.wz-icons [data-state="current"] .wz-ico{background:var(--wz-accent);border-color:var(--wz-accent);color:#fff;box-shadow:0 0 0 5px var(--wz-soft)}
.wz-icons [data-state="current"]{color:#0c1a26;font-weight:600}
.wz-icons [data-state="done"] + li::before,.wz-icons [data-state="current"]::before{background:var(--wz-done)}
@media (max-width:520px){.wz-ico{width:34px;height:34px}.wz-ico svg{width:17px;height:17px}
.wz-icons li + li::before{top:16px;left:calc(-50% + 22px);right:calc(50% + 22px)}
.wz-icons [data-state]:not([data-state="current"]) .wz-name{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}}
/* C. a thin track that fills to the current marker (width from --wz-p, set by the script) */
.wz-line{position:relative;list-style:none;margin:26px 0 0;padding:0;display:grid;grid-template-columns:repeat(var(--wz-n,4),1fr)}
.wz-line::before,.wz-line::after{content:"";position:absolute;top:6px;left:calc(50% / var(--wz-n,4));height:4px;border-radius:4px}
.wz-line::before{right:calc(50% / var(--wz-n,4));background:var(--wz-track)}
.wz-line::after{width:calc((100% - 100% / var(--wz-n,4)) * var(--wz-p,0));background:var(--wz-done);transition:width .4s ease}
.wz-line li{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:10px;text-align:center;font-size:.8rem;line-height:1.3;color:var(--wz-muted)}
.wz-mark{width:16px;height:16px;border-radius:50%;border:2px solid var(--wz-line);background:#fff;transition:border-color .25s,background .25s,box-shadow .25s}
.wz-line [data-state="done"] .wz-mark{background:var(--wz-done);border-color:var(--wz-done)}
.wz-line [data-state="current"] .wz-mark{border:4px solid var(--wz-accent);box-shadow:0 0 0 4px var(--wz-soft)}
.wz-line [data-state="current"]{color:#0c1a26;font-weight:600}
/* D. app header: back arrow, "Step 2 of 4" over the step name and a .progress bar; the real stepper is visually hidden */
.wz-app{max-width:400px;width:100%;margin:0 auto;overflow:hidden}
.wz-appbar{display:grid;grid-template-columns:44px 1fr 44px;align-items:center;gap:6px;padding:10px 10px 12px}
.wz-appbar [data-back]{grid-column:1;grid-row:1}
.wz .wz-iconbtn{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;--bs-btn-border-radius:12px;--bs-btn-color:#0c1a26;--bs-btn-hover-color:#0c1a26;--bs-btn-hover-bg:#f0f5f9;--bs-btn-active-bg:#e3eaf0;width:44px;height:44px;display:grid;place-items:center}
.wz-apptitle{grid-column:2;grid-row:1;margin:0;display:grid;text-align:center;line-height:1.3}
.wz-apptitle span{font-size:.75rem;color:var(--wz-muted)}
.wz-apptitle b{font-size:1rem}
.wz-meter{--bs-progress-height:4px;--bs-progress-bg:var(--wz-track);--bs-progress-border-radius:0;--bs-progress-bar-bg:var(--wz-accent);--bs-progress-bar-transition:width .4s ease}
.wz-meter .progress-bar{width:calc((var(--wz-i,0) + 1) / var(--wz-n,4) * 100%);border-radius:0 4px 4px 0}
.wz-app .wz-panel{margin:0;padding:24px 22px 4px}
.wz-app .wz-done{padding-bottom:24px}
.wz-appfoot{padding:20px 22px 22px}
.wz-appfoot .btn{width:100%}
.wz-done{justify-items:center;text-align:center}
.wz-done .btn{margin-top:10px}
@media (prefers-reduced-motion:reduce){.wz *,.wz *::before,.wz *::after,.wz ::before,.wz ::after{transition:none!important;animation:none!important}}
@media (max-width:600px){.wz .card-body{padding:24px 18px}.wz-app .wz-panel{padding:22px 18px 4px}.wz-appfoot{padding:18px 18px 20px}}
JavaScript
// Stepper wizard: shows one step at a time, marks the stepper with data-state and aria-current="step",
// sets --wz-i / --wz-n / --wz-p (0 to 1) for progress styles and moves focus to the new step's heading.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li'), n = panels.length;
var back = wz.querySelector('[data-back]'), next = wz.querySelector('[data-next]'), finish = wz.querySelector('[data-finish]');
var done = wz.querySelector('[data-done]'), navs = wz.querySelectorAll('[data-nav]'), at = 0;
var count = wz.querySelector('[data-count]'), name = wz.querySelector('[data-name]');
function mark(i) {
var k = Math.min(i, n - 1);
steps.forEach(function (s, j) {
s.dataset.state = j < i ? 'done' : j === i ? 'current' : 'next';
if (j === i) s.setAttribute('aria-current', 'step'); else s.removeAttribute('aria-current');
s.querySelector('[data-sr]').textContent = j < i ? ', completed' : '';
});
wz.style.setProperty('--wz-i', k); wz.style.setProperty('--wz-n', n); wz.style.setProperty('--wz-p', n > 1 ? k / (n - 1) : 1);
if (count) count.textContent = 'Step ' + (k + 1) + ' of ' + n;
if (name) name.textContent = steps[k].firstChild.textContent.trim(); // the li's leading text is the step name
}
function go(i, focus) {
at = i; mark(i);
panels.forEach(function (p, j) { p.hidden = j !== i; });
if (back) back.hidden = i === 0;
next.hidden = i === n - 1;
if (finish) finish.hidden = !next.hidden;
if (focus) panels[i].querySelector('[tabindex="-1"]').focus();
}
if (back) back.addEventListener('click', function () { go(at - 1, true); });
next.addEventListener('click', function () { go(at + 1, true); });
// Last step: do the real thing here (navigate, save). The demo shows the done panel instead.
if (finish && done) finish.addEventListener('click', function () {
panels[at].hidden = true; finish.hidden = true; if (back) back.hidden = true; mark(n);
navs.forEach(function (x) { x.hidden = true; });
done.hidden = false; done.querySelector('[tabindex="-1"]').focus();
});
if (done) done.querySelector('[data-restart]').addEventListener('click', function () {
done.hidden = true; navs.forEach(function (x) { x.hidden = false; }); go(0, true);
});
go(0);
});
// Stepper wizard: shows one step at a time, marks the stepper with data-state and aria-current="step",
// sets --wz-i / --wz-n / --wz-p (0 to 1) for progress styles and moves focus to the new step's heading.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li'), n = panels.length;
var back = wz.querySelector('[data-back]'), next = wz.querySelector('[data-next]'), finish = wz.querySelector('[data-finish]');
var done = wz.querySelector('[data-done]'), navs = wz.querySelectorAll('[data-nav]'), at = 0;
var count = wz.querySelector('[data-count]'), name = wz.querySelector('[data-name]');
function mark(i) {
var k = Math.min(i, n - 1);
steps.forEach(function (s, j) {
s.dataset.state = j < i ? 'done' : j === i ? 'current' : 'next';
if (j === i) s.setAttribute('aria-current', 'step'); else s.removeAttribute('aria-current');
s.querySelector('[data-sr]').textContent = j < i ? ', completed' : '';
});
wz.style.setProperty('--wz-i', k); wz.style.setProperty('--wz-n', n); wz.style.setProperty('--wz-p', n > 1 ? k / (n - 1) : 1);
if (count) count.textContent = 'Step ' + (k + 1) + ' of ' + n;
if (name) name.textContent = steps[k].firstChild.textContent.trim(); // the li's leading text is the step name
}
function go(i, focus) {
at = i; mark(i);
panels.forEach(function (p, j) { p.hidden = j !== i; });
if (back) back.hidden = i === 0;
next.hidden = i === n - 1;
if (finish) finish.hidden = !next.hidden;
if (focus) panels[i].querySelector('[tabindex="-1"]').focus();
}
if (back) back.addEventListener('click', function () { go(at - 1, true); });
next.addEventListener('click', function () { go(at + 1, true); });
// Last step: do the real thing here (navigate, save). The demo shows the done panel instead.
if (finish && done) finish.addEventListener('click', function () {
panels[at].hidden = true; finish.hidden = true; if (back) back.hidden = true; mark(n);
navs.forEach(function (x) { x.hidden = true; });
done.hidden = false; done.querySelector('[tabindex="-1"]').focus();
});
if (done) done.querySelector('[data-restart]').addEventListener('click', function () {
done.hidden = true; navs.forEach(function (x) { x.hidden = false; }); go(0, true);
});
go(0);
});
About this set
Four compact stepper designs, each shown as a small working wizard with Back and Next buttons. Dots is a product tour for a notes app: five small dots where the current one stretches into a pill and finished ones turn teal, with the step names kept for screen readers only. Icons with labels under them is an order tracker with an icon in each circle, connector lines that fill as steps complete and a timestamp line in every panel; on narrow screens only the current label stays visible. Progress line with markers is a mortgage pre-approval with a thin track that fills to the current marker, its width driven by a custom property. Mobile header is an app-style bank account flow with a back arrow, a centred step count over the step name and a thin progress bar, while the real list of steps stays in the page for assistive technology. Use dots for short tours, icons for status and tracking, the line for longer checks with few steps, and the header on small screens. One short vanilla script drives all four: it shows one panel at a time, sets data-state and aria-current on the stepper, writes the progress custom properties and moves focus to each new heading. Without JavaScript every step is visible.
What’s included
Four stepper designs driven by one identical vanilla script
Progress width comes from --wz-p, --wz-i and --wz-n custom properties set by the script
Done states in teal, the current step in sky blue, upcoming steps in grey
Finish button and success panel with a restart action on three of the four wizards
Labels collapse to the current step at phone width, with no horizontal scrolling
All steps show stacked when JavaScript is off
Accessibility
Each stepper is an ordered list labelled Progress, with aria-current="step" on the current item and a visually hidden ", completed" on finished ones. Each step heading starts with its own "Step 2 of 4" text and receives focus on Back and Next, so screen readers announce where you are. In the mobile header the visible step count and progress bar are aria-hidden and the back arrow has an aria-label, so progress is announced once. Buttons have a visible focus ring and transitions are turned off under prefers-reduced-motion.
Customise it
Change the --wz-accent, --wz-done, --wz-soft and --wz-line custom properties on .wz (plain CSS and Bootstrap) or the --color-wz-* values in the Tailwind @theme block. Bootstrap buttons and the progress bar are restyled through their --bs-btn-* and --bs-progress-* variables.