Open demo

CSS code

HTML
<div><span class="lbl">Steps expand in place</span>
<form class="wz" data-wizard aria-labelledby="mf-title">
  <h2 class="wz-title" id="mf-title">Open your shop on Marketfold</h2>
  <p class="wz-sub">Four steps, about three minutes. Nothing goes live until you open the shop.</p>
  <ol class="wz-vsteps" data-stepper aria-label="Progress">
    <li>
      <span class="wz-dot" aria-hidden="true"><b>1</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h1" tabindex="-1"><span class="wz-kick">Step 1 of 4</span>Store details<span class="sr-only" data-sr></span></h3>
        <p class="wz-sum"><span data-out="shop">Marketfold Ceramics</span> · <span data-out="currency">USD</span></p>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h1">
        <div class="wz-row">
          <div class="wz-field"><label for="mf-shop">Shop name</label>
            <input class="wz-input" id="mf-shop" name="shop" required autocomplete="organization" value="Marketfold Ceramics"></div>
          <div class="wz-field"><label for="mf-currency">Currency</label>
            <select class="wz-input" id="mf-currency" name="currency"><option value="USD">US dollar (USD)</option><option value="EUR">Euro (EUR)</option><option value="GBP">Pound sterling (GBP)</option><option value="CAD">Canadian dollar (CAD)</option></select></div>
        </div>
        <div class="wz-field"><label for="mf-handle">Shop address</label>
          <div class="wz-affix"><span>marketfold.com/</span><input class="wz-input" id="mf-handle" name="handle" required pattern="[a-z0-9\-]+" aria-describedby="mf-handle-hint" value="marketfold-ceramics"></div>
          <span class="wz-hint" id="mf-handle-hint">Lowercase letters, numbers and hyphens.</span></div>
        <div class="wz-actions">
          <button type="button" class="wz-btn" data-next hidden>Continue</button>
        </div>
      </section>
    </li>
    <li>
      <span class="wz-dot" aria-hidden="true"><b>2</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h2" tabindex="-1"><span class="wz-kick">Step 2 of 4</span>Shipping<span class="sr-only" data-sr></span></h3>
        <p class="wz-sum">Ships from <span data-out="from">Portugal</span> · <span data-out="processing">3–5 days</span></p>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h2">
        <div class="wz-field"><label for="mf-from">Ships from</label>
          <select class="wz-input" id="mf-from" name="from" required><option>Portugal</option><option>Spain</option><option>United States</option><option>Canada</option><option>United Kingdom</option></select></div>
        <fieldset class="wz-field"><legend class="wz-legend">Processing time</legend>
          <div class="wz-seg">
            <label><input type="radio" name="processing" value="1–2 days" required>1–2 days</label>
            <label><input type="radio" name="processing" value="3–5 days" checked>3–5 days</label>
            <label><input type="radio" name="processing" value="1–2 weeks">1–2 weeks</label>
          </div></fieldset>
        <div class="wz-actions">
          <button type="button" class="wz-btn" data-next hidden>Continue</button>
          <button type="button" class="wz-btn ghost" data-back hidden>Back</button>
        </div>
      </section>
    </li>
    <li>
      <span class="wz-dot" aria-hidden="true"><b>3</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h3" tabindex="-1"><span class="wz-kick">Step 3 of 4</span>Payments<span class="sr-only" data-sr></span></h3>
        <p class="wz-sum"><span data-out="payout">PayPal</span> · <span data-out="account">[email protected]</span></p>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h3">
        <fieldset class="wz-field"><legend class="wz-legend">Payout method</legend>
          <div class="wz-tiles">
            <label class="wz-tile"><input type="radio" name="payout" value="Bank transfer" required><b>Bank transfer</b><span>Every Friday · no fee</span></label>
            <label class="wz-tile"><input type="radio" name="payout" value="PayPal" checked><b>PayPal</b><span>Next working day · 1% fee</span></label>
          </div></fieldset>
        <div class="wz-field"><label for="mf-account">IBAN or PayPal email</label>
          <input class="wz-input" id="mf-account" name="account" required aria-describedby="mf-account-hint" value="[email protected]">
          <span class="wz-hint" id="mf-account-hint">Bank transfer: your IBAN. PayPal: the email on your PayPal account.</span></div>
        <div class="wz-actions">
          <button type="button" class="wz-btn" data-next hidden>Continue</button>
          <button type="button" class="wz-btn ghost" data-back hidden>Back</button>
        </div>
      </section>
    </li>
    <li>
      <span class="wz-dot" aria-hidden="true"><b>4</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h4" tabindex="-1"><span class="wz-kick">Step 4 of 4</span>Launch<span class="sr-only" data-sr></span></h3>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h4">
        <p class="wz-url"><span>Your shop will open at</span><b>marketfold.com/<span data-out="handle">marketfold-ceramics</span></b></p>
        <p class="wz-lead" style="margin:0">Buyers can find you as soon as you open. You can pause the shop from Settings at any time.</p>
        <div class="wz-actions">
          <button type="submit" class="wz-btn">Open shop</button>
          <button type="button" class="wz-btn ghost" data-back hidden>Back</button>
        </div>
      </section>
    </li>
  </ol>

  <section class="wz-panel wz-done" data-done hidden aria-labelledby="mf-h5">
    <span class="wz-dot"><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="mf-h5" tabindex="-1"><span data-out="shop">Marketfold Ceramics</span> is open</h3>
    <p class="wz-lead" style="margin:0">Add your first listing and it appears at marketfold.com/<span data-out="handle">marketfold-ceramics</span>.</p>
    <button type="button" class="wz-btn ghost" data-restart>Set up another shop</button>
  </section>
</form></div>

<div><span class="lbl">Side rail with descriptions</span>
<form class="wz wz-split" data-wizard aria-labelledby="gr-title">
  <div class="wz-rail">
    <h2 class="wz-title" id="gr-title">Grant application</h2>
    <p class="wz-sub">Riverside Arts Council · Spring 2027 round</p>
    <ol class="wz-rsteps" data-stepper aria-label="Progress">
      <li><span class="wz-dot"><b>1</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Applicant</span><span class="wz-desc">Who is applying and how to reach you</span></span><span class="sr-only" data-sr></span></li>
      <li><span class="wz-dot"><b>2</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Project</span><span class="wz-desc">What you will make and when</span></span><span class="sr-only" data-sr></span></li>
      <li><span class="wz-dot"><b>3</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Budget</span><span class="wz-desc">The amount you ask for</span></span><span class="sr-only" data-sr></span></li>
      <li><span class="wz-dot"><b>4</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Attachments</span><span class="wz-desc">Samples and sign-off</span></span><span class="sr-only" data-sr></span></li>
    </ol>
  </div>

  <div class="wz-main">
    <section class="wz-panel" data-step aria-labelledby="gr-h1">
      <h3 class="wz-h" id="gr-h1" tabindex="-1"><span class="wz-kick">Step 1 of 4</span>About the applicant</h3>
      <p class="wz-lead">Individuals and groups based in Riverside County can apply.</p>
      <div class="wz-row">
        <div class="wz-field"><label for="gr-name">Full name</label>
          <input class="wz-input" id="gr-name" name="name" required autocomplete="name" value="Amara Okafor"></div>
        <div class="wz-field"><label for="gr-org">Organisation</label>
          <input class="wz-input" id="gr-org" name="organisation" autocomplete="organization" aria-describedby="gr-org-hint" value="Tidewater Print Collective">
          <span class="wz-hint" id="gr-org-hint">Leave empty if you apply as an individual.</span></div>
      </div>
      <div class="wz-field"><label for="gr-email">Email</label>
        <input class="wz-input" id="gr-email" name="email" type="email" required autocomplete="email" value="[email protected]"></div>
    </section>

    <section class="wz-panel" data-step aria-labelledby="gr-h2">
      <h3 class="wz-h" id="gr-h2" tabindex="-1"><span class="wz-kick">Step 2 of 4</span>Your project</h3>
      <div class="wz-field"><label for="gr-project">Project title</label>
        <input class="wz-input" id="gr-project" name="project" required value="Riverside Linocut Trail"></div>
      <div class="wz-field"><label for="gr-summary">Summary</label>
        <textarea class="wz-input" id="gr-summary" name="summary" required minlength="80" aria-describedby="gr-summary-hint">Twelve large linocut panels printed with residents of the Eastbank estate and mounted along the river path between Mill Bridge and the ferry landing.</textarea>
        <span class="wz-hint" id="gr-summary-hint">At least 80 characters: what you will make, where, and with whom.</span></div>
      <div class="wz-field" style="max-width:240px"><label for="gr-start">Start month</label>
        <input class="wz-input" id="gr-start" name="start" type="month" required min="2027-01" value="2027-03"></div>
    </section>

    <section class="wz-panel" data-step aria-labelledby="gr-h3">
      <h3 class="wz-h" id="gr-h3" tabindex="-1"><span class="wz-kick">Step 3 of 4</span>Budget</h3>
      <div class="wz-row">
        <div class="wz-field"><label for="gr-amount">Amount requested</label>
          <div class="wz-affix"><span>$</span><input class="wz-input" id="gr-amount" name="amount" type="number" required min="500" max="15000" step="50" aria-describedby="gr-amount-hint" value="9800"></div>
          <span class="wz-hint" id="gr-amount-hint">Between $500 and $15,000.</span></div>
        <div class="wz-field"><label for="gr-other">Other funding</label>
          <select class="wz-input" id="gr-other" name="other"><option>None</option><option>Applied, awaiting a decision</option><option>Confirmed</option></select></div>
      </div>
    </section>

    <section class="wz-panel" data-step aria-labelledby="gr-h4">
      <h3 class="wz-h" id="gr-h4" tabindex="-1"><span class="wz-kick">Step 4 of 4</span>Attachments</h3>
      <div class="wz-field"><label for="gr-files">Work samples (optional)</label>
        <input class="wz-input" id="gr-files" name="samples" type="file" multiple accept=".pdf,.jpg,.jpeg,application/pdf,image/jpeg" aria-describedby="gr-files-hint">
        <span class="wz-hint" id="gr-files-hint">PDF or JPG. Photos of earlier work help the panel.</span></div>
      <label class="wz-check"><input type="checkbox" name="confirm" required>I confirm the information is accurate</label>
    </section>

    <section class="wz-panel wz-done" data-done hidden aria-labelledby="gr-h5">
      <span class="wz-dot"><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="gr-h5" tabindex="-1">Application received</h3>
      <p class="wz-ref">Reference <b>RAC-2026-0417</b></p>
      <p class="wz-lead" style="margin:0">We sent a copy to <span data-out="email">[email protected]</span>. The council announces spring awards on 15 January 2027.</p>
      <button type="button" class="wz-btn ghost" data-restart>Start a new application</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>Continue</button>
      <button type="submit" class="wz-btn">Submit application</button>
    </div>
  </div>
</form></div>
CSS
/* Vertical wizard: an <ol> stepper with one <li> per step, one <section data-step> per step and Back / Continue buttons.
   The script hides every step but the current one, sets data-state and aria-current="step" on the stepper
   and moves focus to the new step's heading. Without JavaScript every step shows and the form submits normally. */
.wz{--wz-accent:#047857;--wz-accent-dark:#065f46;--wz-soft:#d1fae5;--wz-tint:#ecfdf5;--wz-ink:#14201a;--wz-muted:#56615a;--wz-line:#808c84;--wz-field:#78847c;--wz-rail:#f6f8f4;
  background:#fff;border-radius:18px;padding:30px 32px;box-shadow:0 1px 2px rgba(20,32,26,.06),0 12px 32px -14px rgba(6,78,59,.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.3rem;font-weight:700;letter-spacing:-.015em;margin:0}
.wz-sub{margin:4px 0 0;color:var(--wz-muted);font-size:.92rem}
.wz-dot{flex:none;width:36px;height:36px;border-radius:50%;display:grid;place-items:center;border:2px solid var(--wz-line);background:#fff;font-weight:700;font-size:.9rem;color:#4a554e;transition:background .25s,border-color .25s,box-shadow .25s}
.wz-dot svg{display:none;width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.wz [data-state="current"] > .wz-dot{border-color:var(--wz-accent);color:var(--wz-accent-dark);box-shadow:0 0 0 5px var(--wz-soft)}
.wz [data-state="done"] > .wz-dot{border-color:var(--wz-accent);background:var(--wz-accent);color:#fff}
.wz [data-state="done"] > .wz-dot b{display:none}
.wz [data-state="done"] > .wz-dot svg{display:block}

/* expand in place: each <li> is dot + header + the step's section; a line joins the dots and fills when a step is done */
.wz-vsteps{list-style:none;margin:26px 0 0;padding:0}
.wz-vsteps > li{position:relative;display:grid;grid-template-columns:36px minmax(0,1fr);column-gap:16px;padding-bottom:22px}
.wz-vsteps > li:last-child{padding-bottom:0}
.wz-vsteps > li:not(:last-child)::before{content:"";position:absolute;left:17px;top:46px;bottom:8px;width:2px;border-radius:2px;background:var(--wz-line);transition:background .25s}
.wz-vsteps > li[data-state="done"]::before{background:var(--wz-accent)}
.wz-vsteps .wz-dot{margin-top:4px}
.wz-head{min-width:0}
.wz-head .wz-h{font-size:1.02rem}
.wz-vsteps [data-state="next"] .wz-h,.wz-vsteps [data-state="next"] .wz-kick{color:var(--wz-muted)}
.wz-vsteps [data-state="next"] .wz-h{font-weight:600}
.wz-sum{display:none;margin:2px 0 0;font-size:.86rem;color:var(--wz-muted);overflow-wrap:anywhere}
.wz-vsteps [data-state="done"] .wz-sum{display:block}
.wz-body{grid-column:2;display:grid;gap:16px;padding-top:16px;animation:wz-in .3s ease}
.wz-actions{display:flex;flex-wrap:wrap;gap:10px;padding-top:4px}
.wz-url{display:grid;gap:4px;margin:0;padding:16px 18px;border-radius:12px;background:var(--wz-tint);border:1px solid #a7e3c9}
.wz-url > span{font-size:.8rem;color:var(--wz-muted)}
.wz-url b{font-size:1.02rem;color:var(--wz-accent-dark);overflow-wrap:anywhere}

/* side rail: steps with descriptions on the left, the current step on the right */
.wz-split{display:grid;grid-template-columns:250px minmax(0,1fr);padding:0;overflow:hidden}
.wz-rail{background:var(--wz-rail);border-right:1px solid #e1e7df;padding:30px 24px}
.wz-main{padding:30px 32px;display:flex;flex-direction:column;min-width:0}
.wz-rsteps{list-style:none;margin:28px 0 0;padding:0}
.wz-rsteps li{position:relative;display:flex;gap:14px;padding-bottom:26px;color:var(--wz-muted)}
.wz-rsteps li:last-child{padding-bottom:0}
.wz-rsteps li:not(:last-child)::before{content:"";position:absolute;left:17px;top:42px;bottom:6px;width:2px;border-radius:2px;background:var(--wz-line);transition:background .25s}
.wz-rsteps li[data-state="done"]::before{background:var(--wz-accent)}
.wz-rtext{display:grid;gap:1px;padding-top:6px;min-width:0}
.wz-name{font-size:.92rem;font-weight:600}
.wz-desc{font-size:.8rem;line-height:1.4}
.wz-rsteps [data-state="current"],.wz-rsteps [data-state="done"]{color:var(--wz-ink)}
.wz-rsteps [data-state="done"] .wz-desc,.wz-rsteps [data-state="current"] .wz-desc{color:var(--wz-muted)}
@media (max-width:640px){
  .wz-split{grid-template-columns:1fr}
  .wz-rail{border-right:0;border-bottom:1px solid #e1e7df;padding:24px 18px 18px}
  .wz-main{padding:24px 18px}
  .wz-rsteps{display:flex;align-items:center;gap:8px;margin-top:18px}
  .wz-rsteps li{padding:0;align-items:center;gap:10px}
  .wz-rsteps li::before,.wz-desc{display:none}
  .wz-rsteps .wz-dot{width:30px;height:30px;font-size:.8rem}
  .wz-rtext{padding:0}
  .wz-rsteps [data-state]:not([data-state="current"]) .wz-rtext{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}}

/* step panels */
.wz-panel{display:grid;gap:18px;animation:wz-in .3s ease}
@keyframes wz-in{from{opacity:0;transform:translateY(6px)}}
.wz-h{margin:0;font-size:1.15rem;font-weight:700;letter-spacing:-.01em}
.wz-h:focus{outline:none}
.wz-kick{display:block;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--wz-accent-dark);margin-bottom:2px}
.wz-lead{margin:-10px 0 0;color:var(--wz-muted);font-size:.92rem}
.wz-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:16px}
.wz-field{display:grid;gap:6px;min-width:0;align-content:start}
.wz-field > label,.wz-legend{font-size:.88rem;font-weight:600}
.wz-hint{font-size:.8rem;color:var(--wz-muted)}
.wz-input{width:100%;font:inherit;font-size:.95rem;color:var(--wz-ink);background:#fff;border:1.5px solid var(--wz-field);border-radius:10px;padding:10px 12px;min-height:44px}
textarea.wz-input{min-height:108px;resize:vertical}
.wz-input:focus-visible{outline:none;border-color:var(--wz-accent);box-shadow:0 0 0 4px var(--wz-soft)}
.wz-input[type="file"]{padding:6px}
.wz-input::file-selector-button{font:inherit;font-size:.88rem;font-weight:600;margin-right:12px;padding:7px 14px;border:0;border-radius:7px;background:var(--wz-soft);color:var(--wz-accent-dark);cursor:pointer}
.wz-affix{display:flex;align-items:stretch;border:1.5px solid var(--wz-field);border-radius:10px;overflow:hidden}
.wz-affix span{display:flex;align-items:center;padding:0 12px;background:var(--wz-rail);color:#4a554e;font-size:.9rem;border-right:1.5px solid var(--wz-field)}
.wz-affix .wz-input{border:0;border-radius:0;min-width:0}
.wz-affix:has(:focus-visible){border-color:var(--wz-accent);box-shadow:0 0 0 4px var(--wz-soft)}
.wz-affix .wz-input:focus-visible{box-shadow:none}
fieldset.wz-field{border:0;margin:0;padding:0}
.wz-legend{padding:0;margin-bottom:6px}

/* choice pills and tiles: a radio inside each label, the outline follows :has(:checked) */
.wz-seg{display:flex;flex-wrap:wrap;gap:8px}
.wz-seg label{display:flex;align-items:center;gap:8px;padding:9px 14px;border:1.5px solid var(--wz-field);border-radius:999px;font-size:.9rem;cursor:pointer;transition:border-color .2s,background .2s}
.wz-seg input{width:16px;height:16px;margin:0;accent-color:var(--wz-accent)}
.wz-seg label:has(:checked){border-color:var(--wz-accent);background:var(--wz-tint);color:var(--wz-accent-dark);font-weight:600}
.wz-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px}
.wz-tile{position:relative;display:grid;gap:2px;padding:14px 14px 14px 44px;border:1.5px solid var(--wz-field);border-radius:12px;cursor:pointer;transition:border-color .2s,background .2s}
.wz-tile input{position:absolute;left:14px;top:16px;width:18px;height:18px;margin:0;accent-color:var(--wz-accent)}
.wz-tile b{font-size:.95rem}
.wz-tile span{font-size:.82rem;color:var(--wz-muted)}
.wz-tile:has(:checked){border-color:var(--wz-accent);background:var(--wz-tint)}
.wz-check{display:flex;align-items:flex-start;gap:10px;font-size:.92rem;cursor:pointer}
.wz-check input{flex:none;width:18px;height:18px;margin:2px 0 0;accent-color:var(--wz-accent)}
.wz-seg input:focus-visible,.wz-tile input:focus-visible,.wz-check input:focus-visible{outline:2px solid var(--wz-accent);outline-offset:3px}

/* buttons */
.wz-nav{display:flex;gap:10px;margin-top:auto;padding-top:20px;border-top:1px solid #e6ebe4}
.wz-main > .wz-panel{margin-bottom:28px}
.wz-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:0 22px;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,box-shadow .2s}
.wz-btn:hover{background:var(--wz-accent-dark)}
.wz-btn: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:var(--wz-tint)}
.wz-nav .wz-btn:not(.ghost){margin-left:auto}
.wz-done{text-align:center;justify-items:center;padding:12px 0 4px}
.wz-vsteps + .wz-done{margin-top:26px;padding-top:26px;border-top:1px solid #e6ebe4}
.wz-done .wz-dot{width:56px;height:56px;border:0;background:var(--wz-soft);color:var(--wz-accent-dark)}
.wz-done .wz-dot svg{display:block;width:26px;height:26px}
.wz-ref{margin:0;padding:8px 16px;border-radius:999px;background:var(--wz-tint);border:1px dashed var(--wz-accent);font-size:.9rem;color:var(--wz-accent-dark)}
@media (prefers-reduced-motion:reduce){.wz *,.wz *::before{transition:none!important;animation:none!important}}
@media (max-width:600px){.wz{padding:24px 18px}.wz-split{padding:0}.wz-vsteps > li{column-gap:12px}}
JavaScript
// Form wizard: shows one step at a time, marks the stepper with data-state and aria-current="step",
// checks the step's fields before moving on and moves focus to the new step's heading.
// Back / Continue can sit in one shared [data-nav] bar or inside every step: one delegated click handler serves both.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
  var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li'), last = panels.length - 1;
  var nav = wz.querySelector('[data-nav]'), submit = wz.querySelector('[type="submit"]'), done = wz.querySelector('[data-done]'), at = 0;
  function heading(p) { return document.getElementById(p.getAttribute('aria-labelledby')); }
  function mark(i) {
    steps.forEach(function (s, n) {
      s.dataset.state = n < i ? 'done' : n === i ? 'current' : 'next';
      if (n === i) s.setAttribute('aria-current', 'step'); else s.removeAttribute('aria-current');
      s.querySelector('[data-sr]').textContent = n < i ? ', completed' : '';
    });
  }
  function go(i, focus) {
    at = i; mark(i);
    panels.forEach(function (p, n) { p.hidden = n !== i; });
    wz.querySelectorAll('[data-back]').forEach(function (b) { b.hidden = i === 0; });
    wz.querySelectorAll('[data-next]').forEach(function (b) { b.hidden = i === last; });
    if (submit) submit.hidden = i !== last;
    wz.querySelectorAll('[data-out]').forEach(function (o) {
      var f = wz.elements[o.dataset.out], v = f && (f.length && !f.options ? [].filter.call(f, function (x) { return x.checked; }).map(function (x) { return x.value; }).join(', ') : f.value);
      o.textContent = v || 'None';
    });
    if (focus) heading(panels[i]).focus();
  }
  function valid() {
    var bad = [].find.call(panels[at].querySelectorAll('input, select, textarea'), function (f) { return !f.checkValidity(); });
    if (bad) bad.reportValidity();
    return !bad;
  }
  wz.noValidate = true;
  wz.addEventListener('click', function (e) {
    var b = e.target.closest('[data-back], [data-next], [data-restart]');
    if (!b) return;
    if (b.hasAttribute('data-back')) go(at - 1, true);
    else if (b.hasAttribute('data-next')) { if (valid()) go(at + 1, true); }
    else { wz.reset(); done.hidden = true; if (nav) nav.hidden = false; go(0, true); }
  });
  wz.addEventListener('submit', function (e) {
    e.preventDefault();
    if (!valid()) return;
    if (at < last) return go(at + 1, true);
    // Last step: send the form here (fetch or wz.submit()). The demo shows the done panel instead.
    if (!done) return;
    panels[at].hidden = true; if (nav) nav.hidden = true; mark(steps.length);
    done.hidden = false; heading(done).focus();
  });
  go(0);
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div><span class="lbl">Steps expand in place</span>
<form class="wz card" data-wizard aria-labelledby="mf-title"><div class="card-body">
  <h2 class="wz-title mb-0" id="mf-title">Open your shop on Marketfold</h2>
  <p class="text-body-secondary mt-1 mb-0 small">Four steps, about three minutes. Nothing goes live until you open the shop.</p>
  <ol class="wz-vsteps" data-stepper aria-label="Progress">
    <li>
      <span class="wz-dot" aria-hidden="true"><b>1</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h1" tabindex="-1"><span class="wz-kick">Step 1 of 4</span>Store details<span class="visually-hidden" data-sr></span></h3>
        <p class="wz-sum"><span data-out="shop">Marketfold Ceramics</span> · <span data-out="currency">USD</span></p>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h1">
        <div class="row g-3">
          <div class="col-sm-6"><label class="form-label" for="mf-shop">Shop name</label>
            <input class="form-control" id="mf-shop" name="shop" required autocomplete="organization" value="Marketfold Ceramics"></div>
          <div class="col-sm-6"><label class="form-label" for="mf-currency">Currency</label>
            <select class="form-select" id="mf-currency" name="currency"><option value="USD">US dollar (USD)</option><option value="EUR">Euro (EUR)</option><option value="GBP">Pound sterling (GBP)</option><option value="CAD">Canadian dollar (CAD)</option></select></div>
        </div>
        <div><label class="form-label" for="mf-handle">Shop address</label>
          <div class="input-group"><span class="input-group-text">marketfold.com/</span><input class="form-control" id="mf-handle" name="handle" required pattern="[a-z0-9\-]+" aria-describedby="mf-handle-hint" value="marketfold-ceramics"></div>
          <div class="form-text" id="mf-handle-hint">Lowercase letters, numbers and hyphens.</div></div>
        <div class="wz-actions">
          <button type="button" class="btn btn-primary" data-next hidden>Continue</button>
        </div>
      </section>
    </li>
    <li>
      <span class="wz-dot" aria-hidden="true"><b>2</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h2" tabindex="-1"><span class="wz-kick">Step 2 of 4</span>Shipping<span class="visually-hidden" data-sr></span></h3>
        <p class="wz-sum">Ships from <span data-out="from">Portugal</span> · <span data-out="processing">3–5 days</span></p>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h2">
        <div><label class="form-label" for="mf-from">Ships from</label>
          <select class="form-select" id="mf-from" name="from" required><option>Portugal</option><option>Spain</option><option>United States</option><option>Canada</option><option>United Kingdom</option></select></div>
        <fieldset><legend>Processing time</legend>
          <div class="wz-seg d-flex flex-wrap gap-2">
            <label><input class="form-check-input" type="radio" name="processing" value="1–2 days" required>1–2 days</label>
            <label><input class="form-check-input" type="radio" name="processing" value="3–5 days" checked>3–5 days</label>
            <label><input class="form-check-input" type="radio" name="processing" value="1–2 weeks">1–2 weeks</label>
          </div></fieldset>
        <div class="wz-actions">
          <button type="button" class="btn btn-primary" data-next hidden>Continue</button>
          <button type="button" class="btn btn-outline-secondary" data-back hidden>Back</button>
        </div>
      </section>
    </li>
    <li>
      <span class="wz-dot" aria-hidden="true"><b>3</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h3" tabindex="-1"><span class="wz-kick">Step 3 of 4</span>Payments<span class="visually-hidden" data-sr></span></h3>
        <p class="wz-sum"><span data-out="payout">PayPal</span> · <span data-out="account">[email protected]</span></p>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h3">
        <fieldset><legend>Payout method</legend>
          <div class="row g-2">
            <div class="col-sm-6"><label class="wz-tile h-100"><input class="form-check-input" type="radio" name="payout" value="Bank transfer" required><b>Bank transfer</b><span>Every Friday · no fee</span></label></div>
            <div class="col-sm-6"><label class="wz-tile h-100"><input class="form-check-input" type="radio" name="payout" value="PayPal" checked><b>PayPal</b><span>Next working day · 1% fee</span></label></div>
          </div></fieldset>
        <div><label class="form-label" for="mf-account">IBAN or PayPal email</label>
          <input class="form-control" id="mf-account" name="account" required aria-describedby="mf-account-hint" value="[email protected]">
          <div class="form-text" id="mf-account-hint">Bank transfer: your IBAN. PayPal: the email on your PayPal account.</div></div>
        <div class="wz-actions">
          <button type="button" class="btn btn-primary" data-next hidden>Continue</button>
          <button type="button" class="btn btn-outline-secondary" data-back hidden>Back</button>
        </div>
      </section>
    </li>
    <li>
      <span class="wz-dot" aria-hidden="true"><b>4</b><svg viewBox="0 0 24 24"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="wz-head">
        <h3 class="wz-h" id="mf-h4" tabindex="-1"><span class="wz-kick">Step 4 of 4</span>Launch<span class="visually-hidden" data-sr></span></h3>
      </div>
      <section class="wz-body" data-step aria-labelledby="mf-h4">
        <p class="wz-url"><span>Your shop will open at</span><b>marketfold.com/<span data-out="handle">marketfold-ceramics</span></b></p>
        <p class="text-body-secondary small mb-0">Buyers can find you as soon as you open. You can pause the shop from Settings at any time.</p>
        <div class="wz-actions">
          <button type="submit" class="btn btn-primary">Open shop</button>
          <button type="button" class="btn btn-outline-secondary" data-back hidden>Back</button>
        </div>
      </section>
    </li>
  </ol>

  <section class="wz-panel wz-done" data-done hidden aria-labelledby="mf-h5">
    <span class="wz-dot"><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="mf-h5" tabindex="-1"><span data-out="shop">Marketfold Ceramics</span> is open</h3>
    <p class="text-body-secondary small mb-0">Add your first listing and it appears at marketfold.com/<span data-out="handle">marketfold-ceramics</span>.</p>
    <button type="button" class="btn btn-outline-secondary" data-restart>Set up another shop</button>
  </section>
</div></form></div>

<div><span class="lbl">Side rail with descriptions</span>
<form class="wz card wz-split" data-wizard aria-labelledby="gr-title">
  <div class="wz-rail">
    <h2 class="wz-title mb-0" id="gr-title">Grant application</h2>
    <p class="text-body-secondary mt-1 mb-0 small">Riverside Arts Council · Spring 2027 round</p>
    <ol class="wz-rsteps" data-stepper aria-label="Progress">
      <li><span class="wz-dot"><b>1</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Applicant</span><span class="wz-desc">Who is applying and how to reach you</span></span><span class="visually-hidden" data-sr></span></li>
      <li><span class="wz-dot"><b>2</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Project</span><span class="wz-desc">What you will make and when</span></span><span class="visually-hidden" data-sr></span></li>
      <li><span class="wz-dot"><b>3</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Budget</span><span class="wz-desc">The amount you ask for</span></span><span class="visually-hidden" data-sr></span></li>
      <li><span class="wz-dot"><b>4</b><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="wz-rtext"><span class="wz-name">Attachments</span><span class="wz-desc">Samples and sign-off</span></span><span class="visually-hidden" data-sr></span></li>
    </ol>
  </div>

  <div class="wz-main">
    <section class="wz-panel" data-step aria-labelledby="gr-h1">
      <h3 class="wz-h" id="gr-h1" tabindex="-1"><span class="wz-kick">Step 1 of 4</span>About the applicant</h3>
      <p class="wz-lead">Individuals and groups based in Riverside County can apply.</p>
      <div class="row g-3">
        <div class="col-sm-6"><label class="form-label" for="gr-name">Full name</label>
          <input class="form-control" id="gr-name" name="name" required autocomplete="name" value="Amara Okafor"></div>
        <div class="col-sm-6"><label class="form-label" for="gr-org">Organisation</label>
          <input class="form-control" id="gr-org" name="organisation" autocomplete="organization" aria-describedby="gr-org-hint" value="Tidewater Print Collective">
          <div class="form-text" id="gr-org-hint">Leave empty if you apply as an individual.</div></div>
      </div>
      <div><label class="form-label" for="gr-email">Email</label>
        <input class="form-control" id="gr-email" name="email" type="email" required autocomplete="email" value="[email protected]"></div>
    </section>

    <section class="wz-panel" data-step aria-labelledby="gr-h2">
      <h3 class="wz-h" id="gr-h2" tabindex="-1"><span class="wz-kick">Step 2 of 4</span>Your project</h3>
      <div><label class="form-label" for="gr-project">Project title</label>
        <input class="form-control" id="gr-project" name="project" required value="Riverside Linocut Trail"></div>
      <div><label class="form-label" for="gr-summary">Summary</label>
        <textarea class="form-control" id="gr-summary" name="summary" rows="4" required minlength="80" aria-describedby="gr-summary-hint">Twelve large linocut panels printed with residents of the Eastbank estate and mounted along the river path between Mill Bridge and the ferry landing.</textarea>
        <div class="form-text" id="gr-summary-hint">At least 80 characters: what you will make, where, and with whom.</div></div>
      <div style="max-width:240px"><label class="form-label" for="gr-start">Start month</label>
        <input class="form-control" id="gr-start" name="start" type="month" required min="2027-01" value="2027-03"></div>
    </section>

    <section class="wz-panel" data-step aria-labelledby="gr-h3">
      <h3 class="wz-h" id="gr-h3" tabindex="-1"><span class="wz-kick">Step 3 of 4</span>Budget</h3>
      <div class="row g-3">
        <div class="col-sm-6"><label class="form-label" for="gr-amount">Amount requested</label>
          <div class="input-group"><span class="input-group-text">$</span><input class="form-control" id="gr-amount" name="amount" type="number" required min="500" max="15000" step="50" aria-describedby="gr-amount-hint" value="9800"></div>
          <div class="form-text" id="gr-amount-hint">Between $500 and $15,000.</div></div>
        <div class="col-sm-6"><label class="form-label" for="gr-other">Other funding</label>
          <select class="form-select" id="gr-other" name="other"><option>None</option><option>Applied, awaiting a decision</option><option>Confirmed</option></select></div>
      </div>
    </section>

    <section class="wz-panel" data-step aria-labelledby="gr-h4">
      <h3 class="wz-h" id="gr-h4" tabindex="-1"><span class="wz-kick">Step 4 of 4</span>Attachments</h3>
      <div><label class="form-label" for="gr-files">Work samples (optional)</label>
        <input class="form-control" id="gr-files" name="samples" type="file" multiple accept=".pdf,.jpg,.jpeg,application/pdf,image/jpeg" aria-describedby="gr-files-hint">
        <div class="form-text" id="gr-files-hint">PDF or JPG. Photos of earlier work help the panel.</div></div>
      <div class="form-check"><input class="form-check-input" type="checkbox" name="confirm" id="gr-confirm" required><label class="form-check-label" for="gr-confirm">I confirm the information is accurate</label></div>
    </section>

    <section class="wz-panel wz-done" data-done hidden aria-labelledby="gr-h5">
      <span class="wz-dot"><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="gr-h5" tabindex="-1">Application received</h3>
      <p class="wz-ref">Reference <b>RAC-2026-0417</b></p>
      <p class="text-body-secondary small mb-0">We sent a copy to <span data-out="email">[email protected]</span>. The council announces spring awards on 15 January 2027.</p>
      <button type="button" class="btn btn-outline-secondary" data-restart>Start a new application</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>Continue</button>
      <button type="submit" class="btn btn-primary">Submit application</button>
    </div>
  </div>
</form></div>
CSS
/* Vertical wizard on Bootstrap 5: .card, .form-control, .form-select, .input-group, .form-check and .btn,
   restyled through Bootstrap's CSS variables. The vertical stepper and the side rail are the custom parts.
   The script hides every step but the current one, sets data-state and aria-current="step" on the stepper
   and moves focus to the new step's heading. Without JavaScript every step shows and the form submits normally. */
.wz{--wz-accent:#047857;--wz-accent-dark:#065f46;--wz-soft:#d1fae5;--wz-tint:#ecfdf5;--wz-muted:#56615a;--wz-line:#808c84;--wz-rail:#f6f8f4;
  --bs-border-color:#78847c;--bs-border-radius:10px;--bs-body-color:#14201a;--bs-secondary-color:#56615a;
  border:0;border-radius:18px;box-shadow:0 1px 2px rgba(20,32,26,.06),0 12px 32px -14px rgba(6,78,59,.22)}
.wz .card-body{padding:30px 32px}
.wz .form-control,.wz .form-select{border-width:1.5px;min-height:44px;padding:10px 12px}
.wz .form-select{padding-right:36px}
.wz .form-control:focus,.wz .form-select:focus{border-color:var(--wz-accent);box-shadow:0 0 0 4px var(--wz-soft)}
.wz textarea.form-control{min-height:108px}
.wz .form-control[type="file"]{padding:6px}
.wz .form-control::file-selector-button{margin:0 12px 0 0;padding:7px 14px;border:0;border-radius:7px;background:var(--wz-soft);color:var(--wz-accent-dark);font-size:.88rem;font-weight:600}
.wz .form-control:hover:not(:disabled):not([readonly])::file-selector-button{background:#bbf2d6}
.wz .input-group-text{border-width:1.5px;background:var(--wz-rail);color:#4a554e;font-size:.9rem}
.wz .input-group:has(:focus) .input-group-text{border-color:var(--wz-accent)}
.wz .form-label,.wz legend{font-size:.88rem;font-weight:600;margin-bottom:6px}
.wz .form-text{font-size:.8rem;color:var(--wz-muted);margin-top:6px}
.wz .form-check-input{border-width:1.5px;border-color:var(--wz-line)}
.wz .form-check-input:checked{background-color:var(--wz-accent);border-color:var(--wz-accent)}
.wz .form-check-input:focus{border-color:var(--wz-accent);box-shadow:0 0 0 3px var(--wz-soft)}
.wz .form-check-input:focus-visible{outline:2px solid var(--wz-accent);outline-offset:2px}
.wz .btn{--bs-btn-padding-x:22px;--bs-btn-font-weight:600;--bs-btn-border-width:1.5px;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-hover-bg:var(--wz-tint);--bs-btn-hover-color:var(--wz-accent-dark);--bs-btn-hover-border-color:var(--wz-line);--bs-btn-active-bg:var(--wz-soft);--bs-btn-active-color:var(--wz-accent-dark);--bs-btn-bg:#fff}
.wz .btn:focus-visible{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--wz-accent);outline:0}
.wz-title{font-size:1.3rem;font-weight:700;letter-spacing:-.015em}
.wz-dot{flex:none;width:36px;height:36px;border-radius:50%;display:grid;place-items:center;border:2px solid var(--wz-line);background:#fff;font-weight:700;font-size:.9rem;color:#4a554e;transition:background .25s,border-color .25s,box-shadow .25s}
.wz-dot svg{display:none;width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}
.wz [data-state="current"] > .wz-dot{border-color:var(--wz-accent);color:var(--wz-accent-dark);box-shadow:0 0 0 5px var(--wz-soft)}
.wz [data-state="done"] > .wz-dot{border-color:var(--wz-accent);background:var(--wz-accent);color:#fff}
.wz [data-state="done"] > .wz-dot b{display:none}
.wz [data-state="done"] > .wz-dot svg{display:block}

/* expand in place: each <li> is dot + header + the step's section; a line joins the dots and fills when a step is done */
.wz-vsteps{list-style:none;margin:26px 0 0;padding:0}
.wz-vsteps > li{position:relative;display:grid;grid-template-columns:36px minmax(0,1fr);column-gap:16px;padding-bottom:22px}
.wz-vsteps > li:last-child{padding-bottom:0}
.wz-vsteps > li:not(:last-child)::before{content:"";position:absolute;left:17px;top:46px;bottom:8px;width:2px;border-radius:2px;background:var(--wz-line);transition:background .25s}
.wz-vsteps > li[data-state="done"]::before{background:var(--wz-accent)}
.wz-vsteps .wz-dot{margin-top:4px}
.wz-head{min-width:0}
.wz-head .wz-h{font-size:1.02rem}
.wz-vsteps [data-state="next"] .wz-h,.wz-vsteps [data-state="next"] .wz-kick{color:var(--wz-muted)}
.wz-vsteps [data-state="next"] .wz-h{font-weight:600}
.wz-sum{display:none;margin:2px 0 0;font-size:.86rem;color:var(--wz-muted);overflow-wrap:anywhere}
.wz-vsteps [data-state="done"] .wz-sum{display:block}
.wz-body{grid-column:2;display:grid;gap:16px;padding-top:16px;animation:wz-in .3s ease}
.wz-actions{display:flex;flex-wrap:wrap;gap:10px;padding-top:4px}
.wz-url{display:grid;gap:4px;margin:0;padding:16px 18px;border-radius:12px;background:var(--wz-tint);border:1px solid #a7e3c9}
.wz-url > span{font-size:.8rem;color:var(--wz-muted)}
.wz-url b{font-size:1.02rem;color:var(--wz-accent-dark);overflow-wrap:anywhere}

/* side rail: steps with descriptions on the left, the current step on the right */
.wz-split{display:grid;grid-template-columns:250px minmax(0,1fr);overflow:hidden}
.wz-rail{background:var(--wz-rail);border-right:1px solid #e1e7df;padding:30px 24px}
.wz-main{padding:30px 32px;display:flex;flex-direction:column;min-width:0}
.wz-rsteps{list-style:none;margin:28px 0 0;padding:0}
.wz-rsteps li{position:relative;display:flex;gap:14px;padding-bottom:26px;color:var(--wz-muted)}
.wz-rsteps li:last-child{padding-bottom:0}
.wz-rsteps li:not(:last-child)::before{content:"";position:absolute;left:17px;top:42px;bottom:6px;width:2px;border-radius:2px;background:var(--wz-line);transition:background .25s}
.wz-rsteps li[data-state="done"]::before{background:var(--wz-accent)}
.wz-rtext{display:grid;gap:1px;padding-top:6px;min-width:0}
.wz-name{font-size:.92rem;font-weight:600}
.wz-desc{font-size:.8rem;line-height:1.4}
.wz-rsteps [data-state="current"],.wz-rsteps [data-state="done"]{color:var(--bs-body-color)}
.wz-rsteps [data-state="done"] .wz-desc,.wz-rsteps [data-state="current"] .wz-desc{color:var(--wz-muted)}
@media (max-width:640px){
  .wz-split{grid-template-columns:1fr}
  .wz-rail{border-right:0;border-bottom:1px solid #e1e7df;padding:24px 18px 18px}
  .wz-main{padding:24px 18px}
  .wz-rsteps{display:flex;align-items:center;gap:8px;margin-top:18px}
  .wz-rsteps li{padding:0;align-items:center;gap:10px}
  .wz-rsteps li::before,.wz-desc{display:none}
  .wz-rsteps .wz-dot{width:30px;height:30px;font-size:.8rem}
  .wz-rtext{padding:0}
  .wz-rsteps [data-state]:not([data-state="current"]) .wz-rtext{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}}

/* step panels */
.wz-panel{display:grid;gap:18px;animation:wz-in .3s ease}
@keyframes wz-in{from{opacity:0;transform:translateY(6px)}}
.wz-h{margin:0;font-size:1.15rem;font-weight:700;letter-spacing:-.01em;line-height:1.5}
.wz-h:focus{outline:none}
.wz-kick{display:block;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--wz-accent-dark);margin-bottom:2px}
.wz-lead{margin:-10px 0 0;color:var(--wz-muted);font-size:.92rem}

/* choice pills and tiles: a .form-check-input inside each label, the outline follows :has(:checked) */
.wz-seg label{display:flex;align-items:center;gap:8px;padding:9px 14px;border:1.5px solid var(--bs-border-color);border-radius:999px;font-size:.9rem;cursor:pointer;transition:border-color .2s,background .2s}
.wz-seg .form-check-input{width:16px;height:16px;margin:0}
.wz-seg label:has(:checked){border-color:var(--wz-accent);background:var(--wz-tint);color:var(--wz-accent-dark);font-weight:600}
.wz-tile{position:relative;display:grid;gap:2px;padding:14px 14px 14px 44px;border:1.5px solid var(--bs-border-color);border-radius:12px;cursor:pointer;transition:border-color .2s,background .2s}
.wz-tile .form-check-input{position:absolute;left:14px;top:16px;width:18px;height:18px;margin:0}
.wz-tile b{font-size:.95rem}
.wz-tile span{font-size:.82rem;color:var(--wz-muted)}
.wz-tile:has(:checked){border-color:var(--wz-accent);background:var(--wz-tint)}
.wz .form-check{display:flex;align-items:flex-start;gap:10px;padding-left:0;font-size:.92rem}
.wz .form-check .form-check-input{flex:none;width:18px;height:18px;margin:2px 0 0;float:none}

/* buttons */
.wz-nav{display:flex;gap:8px;margin-top:auto;padding-top:20px;border-top:1px solid #e6ebe4}
.wz-main > .wz-panel{margin-bottom:28px}
.wz-nav .btn-primary{margin-left:auto}
.wz-done{text-align:center;justify-items:center;padding:12px 0 4px}
.wz-vsteps + .wz-done{margin-top:26px;padding-top:26px;border-top:1px solid #e6ebe4}
.wz-done .wz-dot{width:56px;height:56px;border:0;background:var(--wz-soft);color:var(--wz-accent-dark)}
.wz-done .wz-dot svg{display:block;width:26px;height:26px}
.wz-ref{margin:0;padding:8px 16px;border-radius:999px;background:var(--wz-tint);border:1px dashed var(--wz-accent);font-size:.9rem;color:var(--wz-accent-dark)}
@media (prefers-reduced-motion:reduce){.wz *,.wz *::before{transition:none!important;animation:none!important}}
@media (max-width:600px){.wz .card-body{padding:24px 18px}.wz-vsteps > li{column-gap:12px}}
JavaScript
// Form wizard: shows one step at a time, marks the stepper with data-state and aria-current="step",
// checks the step's fields before moving on and moves focus to the new step's heading.
// Back / Continue can sit in one shared [data-nav] bar or inside every step: one delegated click handler serves both.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
  var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li'), last = panels.length - 1;
  var nav = wz.querySelector('[data-nav]'), submit = wz.querySelector('[type="submit"]'), done = wz.querySelector('[data-done]'), at = 0;
  function heading(p) { return document.getElementById(p.getAttribute('aria-labelledby')); }
  function mark(i) {
    steps.forEach(function (s, n) {
      s.dataset.state = n < i ? 'done' : n === i ? 'current' : 'next';
      if (n === i) s.setAttribute('aria-current', 'step'); else s.removeAttribute('aria-current');
      s.querySelector('[data-sr]').textContent = n < i ? ', completed' : '';
    });
  }
  function go(i, focus) {
    at = i; mark(i);
    panels.forEach(function (p, n) { p.hidden = n !== i; });
    wz.querySelectorAll('[data-back]').forEach(function (b) { b.hidden = i === 0; });
    wz.querySelectorAll('[data-next]').forEach(function (b) { b.hidden = i === last; });
    if (submit) submit.hidden = i !== last;
    wz.querySelectorAll('[data-out]').forEach(function (o) {
      var f = wz.elements[o.dataset.out], v = f && (f.length && !f.options ? [].filter.call(f, function (x) { return x.checked; }).map(function (x) { return x.value; }).join(', ') : f.value);
      o.textContent = v || 'None';
    });
    if (focus) heading(panels[i]).focus();
  }
  function valid() {
    var bad = [].find.call(panels[at].querySelectorAll('input, select, textarea'), function (f) { return !f.checkValidity(); });
    if (bad) bad.reportValidity();
    return !bad;
  }
  wz.noValidate = true;
  wz.addEventListener('click', function (e) {
    var b = e.target.closest('[data-back], [data-next], [data-restart]');
    if (!b) return;
    if (b.hasAttribute('data-back')) go(at - 1, true);
    else if (b.hasAttribute('data-next')) { if (valid()) go(at + 1, true); }
    else { wz.reset(); done.hidden = true; if (nav) nav.hidden = false; go(0, true); }
  });
  wz.addEventListener('submit', function (e) {
    e.preventDefault();
    if (!valid()) return;
    if (at < last) return go(at + 1, true);
    // Last step: send the form here (fetch or wz.submit()). The demo shows the done panel instead.
    if (!done) return;
    panels[at].hidden = true; if (nav) nav.hidden = true; mark(steps.length);
    done.hidden = false; heading(done).focus();
  });
  go(0);
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-wz-muted uppercase">Steps expand in place</span>
<form class="rounded-[18px] bg-white text-wz-ink shadow-[0_1px_2px_rgba(20,32,26,.06),0_12px_32px_-14px_rgba(6,78,59,.22)] px-8 py-[30px] max-[600px]:px-[18px] max-[600px]:py-6" data-wizard aria-labelledby="mf-title">
  <h2 class="text-[1.3rem] font-bold tracking-[-.015em]" id="mf-title">Open your shop on Marketfold</h2>
  <p class="mt-1 text-[.92rem] text-wz-muted">Four steps, about three minutes. Nothing goes live until you open the shop.</p>
  <ol class="mt-[26px]" data-stepper aria-label="Progress">
    <li class="group relative grid grid-cols-[36px_minmax(0,1fr)] gap-x-4 pb-[22px] last:pb-0 not-last:before:absolute not-last:before:top-[46px] not-last:before:bottom-2 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none max-[600px]:gap-x-3">
      <span class="mt-1 grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none" aria-hidden="true"><b class="group-data-[state=done]:hidden">1</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="min-w-0">
        <h3 class="text-[1.02rem] font-bold tracking-[-.01em] focus:outline-none group-data-[state=next]:font-semibold group-data-[state=next]:text-wz-muted" id="mf-h1" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase group-data-[state=next]:text-wz-muted">Step 1 of 4</span>Store details<span class="sr-only" data-sr></span></h3>
        <p class="mt-0.5 hidden text-[.86rem] text-wz-muted wrap-anywhere group-data-[state=done]:block"><span data-out="shop">Marketfold Ceramics</span> · <span data-out="currency">USD</span></p>
      </div>
      <section class="col-start-2 grid animate-wz-in gap-4 pt-4 motion-reduce:animate-none" data-step aria-labelledby="mf-h1">
        <div class="grid grid-cols-[repeat(auto-fit,minmax(190px,1fr))] gap-4">
          <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="mf-shop">Shop name</label>
            <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="mf-shop" name="shop" required autocomplete="organization" value="Marketfold Ceramics"></div>
          <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="mf-currency">Currency</label>
            <select class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="mf-currency" name="currency"><option value="USD">US dollar (USD)</option><option value="EUR">Euro (EUR)</option><option value="GBP">Pound sterling (GBP)</option><option value="CAD">Canadian dollar (CAD)</option></select></div>
        </div>
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="mf-handle">Shop address</label>
          <div class="flex overflow-hidden rounded-[10px] border-[1.5px] border-wz-field has-[:focus-visible]:border-wz-accent has-[:focus-visible]:shadow-[0_0_0_4px_var(--color-wz-soft)]"><span class="flex items-center border-r-[1.5px] border-wz-field bg-wz-rail px-3 text-[.9rem] text-[#4a554e]">marketfold.com/</span><input class="min-h-11 w-full min-w-0 bg-white px-3 py-2.5 text-[.95rem] focus-visible:outline-none" id="mf-handle" name="handle" required pattern="[a-z0-9\-]+" aria-describedby="mf-handle-hint" value="marketfold-ceramics"></div>
          <span class="text-[.8rem] text-wz-muted" id="mf-handle-hint">Lowercase letters, numbers and hyphens.</span></div>
        <div class="flex flex-wrap gap-2.5 pt-1">
          <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-accent bg-wz-accent px-[22px] text-[.95rem] font-semibold text-white transition-colors hover:bg-wz-dark focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-next hidden>Continue</button>
        </div>
      </section>
    </li>
    <li class="group relative grid grid-cols-[36px_minmax(0,1fr)] gap-x-4 pb-[22px] last:pb-0 not-last:before:absolute not-last:before:top-[46px] not-last:before:bottom-2 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none max-[600px]:gap-x-3">
      <span class="mt-1 grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none" aria-hidden="true"><b class="group-data-[state=done]:hidden">2</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="min-w-0">
        <h3 class="text-[1.02rem] font-bold tracking-[-.01em] focus:outline-none group-data-[state=next]:font-semibold group-data-[state=next]:text-wz-muted" id="mf-h2" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase group-data-[state=next]:text-wz-muted">Step 2 of 4</span>Shipping<span class="sr-only" data-sr></span></h3>
        <p class="mt-0.5 hidden text-[.86rem] text-wz-muted wrap-anywhere group-data-[state=done]:block">Ships from <span data-out="from">Portugal</span> · <span data-out="processing">3–5 days</span></p>
      </div>
      <section class="col-start-2 grid animate-wz-in gap-4 pt-4 motion-reduce:animate-none" data-step aria-labelledby="mf-h2">
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="mf-from">Ships from</label>
          <select class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="mf-from" name="from" required><option>Portugal</option><option>Spain</option><option>United States</option><option>Canada</option><option>United Kingdom</option></select></div>
        <fieldset class="grid gap-1.5"><legend class="mb-1.5 text-[.88rem] font-semibold">Processing time</legend>
          <div class="flex flex-wrap gap-2">
            <label class="flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-wz-field px-3.5 py-[9px] text-[.9rem] transition-colors has-checked:border-wz-accent has-checked:bg-wz-tint has-checked:font-semibold has-checked:text-wz-dark motion-reduce:transition-none"><input class="size-4 accent-wz-accent focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-wz-accent" type="radio" name="processing" value="1–2 days" required>1–2 days</label>
            <label class="flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-wz-field px-3.5 py-[9px] text-[.9rem] transition-colors has-checked:border-wz-accent has-checked:bg-wz-tint has-checked:font-semibold has-checked:text-wz-dark motion-reduce:transition-none"><input class="size-4 accent-wz-accent focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-wz-accent" type="radio" name="processing" value="3–5 days" checked>3–5 days</label>
            <label class="flex cursor-pointer items-center gap-2 rounded-full border-[1.5px] border-wz-field px-3.5 py-[9px] text-[.9rem] transition-colors has-checked:border-wz-accent has-checked:bg-wz-tint has-checked:font-semibold has-checked:text-wz-dark motion-reduce:transition-none"><input class="size-4 accent-wz-accent focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-wz-accent" type="radio" name="processing" value="1–2 weeks">1–2 weeks</label>
          </div></fieldset>
        <div class="flex flex-wrap gap-2.5 pt-1">
          <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-accent bg-wz-accent px-[22px] text-[.95rem] font-semibold text-white transition-colors hover:bg-wz-dark focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-next hidden>Continue</button>
          <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-line bg-white px-[22px] text-[.95rem] font-semibold text-wz-dark transition-colors hover:bg-wz-tint focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-back hidden>Back</button>
        </div>
      </section>
    </li>
    <li class="group relative grid grid-cols-[36px_minmax(0,1fr)] gap-x-4 pb-[22px] last:pb-0 not-last:before:absolute not-last:before:top-[46px] not-last:before:bottom-2 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none max-[600px]:gap-x-3">
      <span class="mt-1 grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none" aria-hidden="true"><b class="group-data-[state=done]:hidden">3</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="min-w-0">
        <h3 class="text-[1.02rem] font-bold tracking-[-.01em] focus:outline-none group-data-[state=next]:font-semibold group-data-[state=next]:text-wz-muted" id="mf-h3" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase group-data-[state=next]:text-wz-muted">Step 3 of 4</span>Payments<span class="sr-only" data-sr></span></h3>
        <p class="mt-0.5 hidden text-[.86rem] text-wz-muted wrap-anywhere group-data-[state=done]:block"><span data-out="payout">PayPal</span> · <span data-out="account">[email protected]</span></p>
      </div>
      <section class="col-start-2 grid animate-wz-in gap-4 pt-4 motion-reduce:animate-none" data-step aria-labelledby="mf-h3">
        <fieldset class="grid gap-1.5"><legend class="mb-1.5 text-[.88rem] font-semibold">Payout method</legend>
          <div class="grid grid-cols-[repeat(auto-fit,minmax(180px,1fr))] gap-3">
            <label class="relative grid cursor-pointer gap-0.5 rounded-xl border-[1.5px] border-wz-field py-3.5 pr-3.5 pl-11 transition-colors has-checked:border-wz-accent has-checked:bg-wz-tint motion-reduce:transition-none"><input class="absolute top-4 left-3.5 size-[18px] accent-wz-accent focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-wz-accent" type="radio" name="payout" value="Bank transfer" required><b class="text-[.95rem]">Bank transfer</b><span class="text-[.82rem] text-wz-muted">Every Friday · no fee</span></label>
            <label class="relative grid cursor-pointer gap-0.5 rounded-xl border-[1.5px] border-wz-field py-3.5 pr-3.5 pl-11 transition-colors has-checked:border-wz-accent has-checked:bg-wz-tint motion-reduce:transition-none"><input class="absolute top-4 left-3.5 size-[18px] accent-wz-accent focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-wz-accent" type="radio" name="payout" value="PayPal" checked><b class="text-[.95rem]">PayPal</b><span class="text-[.82rem] text-wz-muted">Next working day · 1% fee</span></label>
          </div></fieldset>
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="mf-account">IBAN or PayPal email</label>
          <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="mf-account" name="account" required aria-describedby="mf-account-hint" value="[email protected]">
          <span class="text-[.8rem] text-wz-muted" id="mf-account-hint">Bank transfer: your IBAN. PayPal: the email on your PayPal account.</span></div>
        <div class="flex flex-wrap gap-2.5 pt-1">
          <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-accent bg-wz-accent px-[22px] text-[.95rem] font-semibold text-white transition-colors hover:bg-wz-dark focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-next hidden>Continue</button>
          <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-line bg-white px-[22px] text-[.95rem] font-semibold text-wz-dark transition-colors hover:bg-wz-tint focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-back hidden>Back</button>
        </div>
      </section>
    </li>
    <li class="group relative grid grid-cols-[36px_minmax(0,1fr)] gap-x-4 pb-[22px] last:pb-0 not-last:before:absolute not-last:before:top-[46px] not-last:before:bottom-2 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none max-[600px]:gap-x-3">
      <span class="mt-1 grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none" aria-hidden="true"><b class="group-data-[state=done]:hidden">4</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <div class="min-w-0">
        <h3 class="text-[1.02rem] font-bold tracking-[-.01em] focus:outline-none group-data-[state=next]:font-semibold group-data-[state=next]:text-wz-muted" id="mf-h4" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase group-data-[state=next]:text-wz-muted">Step 4 of 4</span>Launch<span class="sr-only" data-sr></span></h3>
      </div>
      <section class="col-start-2 grid animate-wz-in gap-4 pt-4 motion-reduce:animate-none" data-step aria-labelledby="mf-h4">
        <p class="grid gap-1 rounded-xl border border-[#a7e3c9] bg-wz-tint px-[18px] py-4"><span class="text-[.8rem] text-wz-muted">Your shop will open at</span><b class="text-[1.02rem] text-wz-dark wrap-anywhere">marketfold.com/<span data-out="handle">marketfold-ceramics</span></b></p>
        <p class="text-[.92rem] text-wz-muted">Buyers can find you as soon as you open. You can pause the shop from Settings at any time.</p>
        <div class="flex flex-wrap gap-2.5 pt-1">
          <button type="submit" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-accent bg-wz-accent px-[22px] text-[.95rem] font-semibold text-white transition-colors hover:bg-wz-dark focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none">Open shop</button>
          <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-line bg-white px-[22px] text-[.95rem] font-semibold text-wz-dark transition-colors hover:bg-wz-tint focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-back hidden>Back</button>
        </div>
      </section>
    </li>
  </ol>

  <section class="mt-[26px] grid animate-wz-in justify-items-center gap-[18px] border-t border-[#e6ebe4] pt-[26px] pb-1 text-center motion-reduce:animate-none" data-done hidden aria-labelledby="mf-h5">
    <span class="grid size-14 place-items-center rounded-full bg-wz-soft text-wz-dark"><svg class="size-[26px] fill-none stroke-current stroke-3" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
    <h3 class="text-[1.15rem] font-bold tracking-[-.01em] focus:outline-none" id="mf-h5" tabindex="-1"><span data-out="shop">Marketfold Ceramics</span> is open</h3>
    <p class="text-[.92rem] text-wz-muted">Add your first listing and it appears at marketfold.com/<span data-out="handle">marketfold-ceramics</span>.</p>
    <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-line bg-white px-[22px] text-[.95rem] font-semibold text-wz-dark transition-colors hover:bg-wz-tint focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-restart>Set up another shop</button>
  </section>
</form></div>

<div><span class="mb-3 block text-[.72rem] tracking-[.12em] text-wz-muted uppercase">Side rail with descriptions</span>
<form class="rounded-[18px] bg-white text-wz-ink shadow-[0_1px_2px_rgba(20,32,26,.06),0_12px_32px_-14px_rgba(6,78,59,.22)] grid grid-cols-[250px_minmax(0,1fr)] overflow-hidden max-[640px]:grid-cols-1" data-wizard aria-labelledby="gr-title">
  <div class="border-r border-[#e1e7df] bg-wz-rail px-6 py-[30px] max-[640px]:border-r-0 max-[640px]:border-b max-[640px]:px-[18px] max-[640px]:pt-6 max-[640px]:pb-[18px]">
    <h2 class="text-[1.3rem] font-bold tracking-[-.015em]" id="gr-title">Grant application</h2>
    <p class="mt-1 text-[.92rem] text-wz-muted">Riverside Arts Council · Spring 2027 round</p>
    <ol class="mt-7 max-[640px]:mt-[18px] max-[640px]:flex max-[640px]:items-center max-[640px]:gap-2" data-stepper aria-label="Progress">
      <li class="group relative flex gap-3.5 pb-[26px] text-wz-muted last:pb-0 not-last:before:absolute not-last:before:top-[42px] not-last:before:bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none data-[state=current]:text-wz-ink data-[state=done]:text-wz-ink max-[640px]:items-center max-[640px]:gap-2.5 max-[640px]:p-0 not-last:max-[640px]:before:hidden"><span class="grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none max-[640px]:size-[30px] max-[640px]:text-[.8rem]"><b class="group-data-[state=done]:hidden">1</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="grid min-w-0 gap-px pt-1.5 max-[640px]:pt-0 max-[640px]:group-data-[state=done]:sr-only max-[640px]:group-data-[state=next]:sr-only"><span class="text-[.92rem] font-semibold">Applicant</span><span class="text-[.8rem] leading-[1.4] text-wz-muted max-[640px]:hidden">Who is applying and how to reach you</span></span><span class="sr-only" data-sr></span></li>
      <li class="group relative flex gap-3.5 pb-[26px] text-wz-muted last:pb-0 not-last:before:absolute not-last:before:top-[42px] not-last:before:bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none data-[state=current]:text-wz-ink data-[state=done]:text-wz-ink max-[640px]:items-center max-[640px]:gap-2.5 max-[640px]:p-0 not-last:max-[640px]:before:hidden"><span class="grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none max-[640px]:size-[30px] max-[640px]:text-[.8rem]"><b class="group-data-[state=done]:hidden">2</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="grid min-w-0 gap-px pt-1.5 max-[640px]:pt-0 max-[640px]:group-data-[state=done]:sr-only max-[640px]:group-data-[state=next]:sr-only"><span class="text-[.92rem] font-semibold">Project</span><span class="text-[.8rem] leading-[1.4] text-wz-muted max-[640px]:hidden">What you will make and when</span></span><span class="sr-only" data-sr></span></li>
      <li class="group relative flex gap-3.5 pb-[26px] text-wz-muted last:pb-0 not-last:before:absolute not-last:before:top-[42px] not-last:before:bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none data-[state=current]:text-wz-ink data-[state=done]:text-wz-ink max-[640px]:items-center max-[640px]:gap-2.5 max-[640px]:p-0 not-last:max-[640px]:before:hidden"><span class="grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none max-[640px]:size-[30px] max-[640px]:text-[.8rem]"><b class="group-data-[state=done]:hidden">3</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="grid min-w-0 gap-px pt-1.5 max-[640px]:pt-0 max-[640px]:group-data-[state=done]:sr-only max-[640px]:group-data-[state=next]:sr-only"><span class="text-[.92rem] font-semibold">Budget</span><span class="text-[.8rem] leading-[1.4] text-wz-muted max-[640px]:hidden">The amount you ask for</span></span><span class="sr-only" data-sr></span></li>
      <li class="group relative flex gap-3.5 pb-[26px] text-wz-muted last:pb-0 not-last:before:absolute not-last:before:top-[42px] not-last:before:bottom-1.5 not-last:before:left-[17px] not-last:before:w-0.5 not-last:before:rounded-sm not-last:before:bg-wz-line not-last:before:transition-colors not-last:data-[state=done]:before:bg-wz-accent not-last:motion-reduce:before:transition-none data-[state=current]:text-wz-ink data-[state=done]:text-wz-ink max-[640px]:items-center max-[640px]:gap-2.5 max-[640px]:p-0 not-last:max-[640px]:before:hidden"><span class="grid size-9 flex-none place-items-center rounded-full border-2 border-wz-line bg-white text-[.9rem] font-bold text-[#4a554e] transition-[background-color,border-color,box-shadow] duration-250 group-data-[state=current]:border-wz-accent group-data-[state=current]:text-wz-dark group-data-[state=current]:shadow-[0_0_0_5px_var(--color-wz-soft)] group-data-[state=done]:border-wz-accent group-data-[state=done]:bg-wz-accent group-data-[state=done]:text-white motion-reduce:transition-none max-[640px]:size-[30px] max-[640px]:text-[.8rem]"><b class="group-data-[state=done]:hidden">4</b><svg class="hidden size-[18px] fill-none stroke-current stroke-3 group-data-[state=done]:block" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span><span class="grid min-w-0 gap-px pt-1.5 max-[640px]:pt-0 max-[640px]:group-data-[state=done]:sr-only max-[640px]:group-data-[state=next]:sr-only"><span class="text-[.92rem] font-semibold">Attachments</span><span class="text-[.8rem] leading-[1.4] text-wz-muted max-[640px]:hidden">Samples and sign-off</span></span><span class="sr-only" data-sr></span></li>
    </ol>
  </div>

  <div class="flex min-w-0 flex-col px-8 py-[30px] max-[640px]:px-[18px] max-[640px]:py-6">
    <section class="mb-7 grid animate-wz-in gap-[18px] motion-reduce:animate-none" data-step aria-labelledby="gr-h1">
      <h3 class="text-[1.15rem] font-bold tracking-[-.01em] focus:outline-none" id="gr-h1" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase">Step 1 of 4</span>About the applicant</h3>
      <p class="-mt-2.5 text-[.92rem] text-wz-muted">Individuals and groups based in Riverside County can apply.</p>
      <div class="grid grid-cols-[repeat(auto-fit,minmax(190px,1fr))] gap-4">
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-name">Full name</label>
          <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-name" name="name" required autocomplete="name" value="Amara Okafor"></div>
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-org">Organisation</label>
          <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-org" name="organisation" autocomplete="organization" aria-describedby="gr-org-hint" value="Tidewater Print Collective">
          <span class="text-[.8rem] text-wz-muted" id="gr-org-hint">Leave empty if you apply as an individual.</span></div>
      </div>
      <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-email">Email</label>
        <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-email" name="email" type="email" required autocomplete="email" value="[email protected]"></div>
    </section>

    <section class="mb-7 grid animate-wz-in gap-[18px] motion-reduce:animate-none" data-step aria-labelledby="gr-h2">
      <h3 class="text-[1.15rem] font-bold tracking-[-.01em] focus:outline-none" id="gr-h2" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase">Step 2 of 4</span>Your project</h3>
      <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-project">Project title</label>
        <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-project" name="project" required value="Riverside Linocut Trail"></div>
      <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-summary">Summary</label>
        <textarea class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none min-h-[108px] resize-y" id="gr-summary" name="summary" required minlength="80" aria-describedby="gr-summary-hint">Twelve large linocut panels printed with residents of the Eastbank estate and mounted along the river path between Mill Bridge and the ferry landing.</textarea>
        <span class="text-[.8rem] text-wz-muted" id="gr-summary-hint">At least 80 characters: what you will make, where, and with whom.</span></div>
      <div class="grid min-w-0 content-start gap-1.5 max-w-[240px]"><label class="text-[.88rem] font-semibold" for="gr-start">Start month</label>
        <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-start" name="start" type="month" required min="2027-01" value="2027-03"></div>
    </section>

    <section class="mb-7 grid animate-wz-in gap-[18px] motion-reduce:animate-none" data-step aria-labelledby="gr-h3">
      <h3 class="text-[1.15rem] font-bold tracking-[-.01em] focus:outline-none" id="gr-h3" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase">Step 3 of 4</span>Budget</h3>
      <div class="grid grid-cols-[repeat(auto-fit,minmax(190px,1fr))] gap-4">
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-amount">Amount requested</label>
          <div class="flex overflow-hidden rounded-[10px] border-[1.5px] border-wz-field has-[:focus-visible]:border-wz-accent has-[:focus-visible]:shadow-[0_0_0_4px_var(--color-wz-soft)]"><span class="flex items-center border-r-[1.5px] border-wz-field bg-wz-rail px-3 text-[.9rem] text-[#4a554e]">$</span><input class="min-h-11 w-full min-w-0 bg-white px-3 py-2.5 text-[.95rem] focus-visible:outline-none" id="gr-amount" name="amount" type="number" required min="500" max="15000" step="50" aria-describedby="gr-amount-hint" value="9800"></div>
          <span class="text-[.8rem] text-wz-muted" id="gr-amount-hint">Between $500 and $15,000.</span></div>
        <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-other">Other funding</label>
          <select class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white px-3 py-2.5 text-[.95rem] focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-other" name="other"><option>None</option><option>Applied, awaiting a decision</option><option>Confirmed</option></select></div>
      </div>
    </section>

    <section class="mb-7 grid animate-wz-in gap-[18px] motion-reduce:animate-none" data-step aria-labelledby="gr-h4">
      <h3 class="text-[1.15rem] font-bold tracking-[-.01em] focus:outline-none" id="gr-h4" tabindex="-1"><span class="mb-0.5 block text-[.72rem] font-bold tracking-[.1em] text-wz-dark uppercase">Step 4 of 4</span>Attachments</h3>
      <div class="grid min-w-0 content-start gap-1.5"><label class="text-[.88rem] font-semibold" for="gr-files">Work samples (optional)</label>
        <input class="min-h-11 w-full rounded-[10px] border-[1.5px] border-wz-field bg-white p-1.5 text-[.95rem] file:mr-3 file:cursor-pointer file:rounded-[7px] file:border-0 file:bg-wz-soft file:px-3.5 file:py-[7px] file:text-[.88rem] file:font-semibold file:text-wz-dark focus-visible:border-wz-accent focus-visible:shadow-[0_0_0_4px_var(--color-wz-soft)] focus-visible:outline-none" id="gr-files" name="samples" type="file" multiple accept=".pdf,.jpg,.jpeg,application/pdf,image/jpeg" aria-describedby="gr-files-hint">
        <span class="text-[.8rem] text-wz-muted" id="gr-files-hint">PDF or JPG. Photos of earlier work help the panel.</span></div>
      <label class="flex cursor-pointer items-start gap-2.5 text-[.92rem]"><input class="mt-0.5 size-[18px] flex-none accent-wz-accent focus-visible:outline-2 focus-visible:outline-offset-3 focus-visible:outline-wz-accent" type="checkbox" name="confirm" required>I confirm the information is accurate</label>
    </section>

    <section class="mb-7 grid animate-wz-in justify-items-center gap-[18px] pt-3 pb-1 text-center motion-reduce:animate-none" data-done hidden aria-labelledby="gr-h5">
      <span class="grid size-14 place-items-center rounded-full bg-wz-soft text-wz-dark"><svg class="size-[26px] fill-none stroke-current stroke-3" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
      <h3 class="text-[1.15rem] font-bold tracking-[-.01em] focus:outline-none" id="gr-h5" tabindex="-1">Application received</h3>
      <p class="rounded-full border border-dashed border-wz-accent bg-wz-tint px-4 py-2 text-[.9rem] text-wz-dark">Reference <b>RAC-2026-0417</b></p>
      <p class="text-[.92rem] text-wz-muted">We sent a copy to <span data-out="email">[email protected]</span>. The council announces spring awards on 15 January 2027.</p>
      <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-line bg-white px-[22px] text-[.95rem] font-semibold text-wz-dark transition-colors hover:bg-wz-tint focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-restart>Start a new application</button>
    </section>

    <div class="mt-auto flex gap-2.5 border-t border-[#e6ebe4] pt-5" data-nav>
      <button type="button" class="inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-line bg-white px-[22px] text-[.95rem] font-semibold text-wz-dark transition-colors hover:bg-wz-tint focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-back hidden>Back</button>
      <button type="button" class="ms-auto inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-accent bg-wz-accent px-[22px] text-[.95rem] font-semibold text-white transition-colors hover:bg-wz-dark focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none" data-next hidden>Continue</button>
      <button type="submit" class="ms-auto inline-flex min-h-11 cursor-pointer items-center justify-center rounded-[10px] border-[1.5px] border-wz-accent bg-wz-accent px-[22px] text-[.95rem] font-semibold text-white transition-colors hover:bg-wz-dark focus-visible:shadow-[0_0_0_3px_#fff,0_0_0_5px_var(--color-wz-accent)] focus-visible:outline-none motion-reduce:transition-none">Submit application</button>
    </div>
  </div>
</form></div>
CSS
@theme {
  --color-wz-accent: #047857;
  --color-wz-dark: #065f46;
  --color-wz-soft: #d1fae5;
  --color-wz-tint: #ecfdf5;
  --color-wz-ink: #14201a;
  --color-wz-muted: #56615a;
  --color-wz-line: #808c84;
  --color-wz-field: #78847c;
  --color-wz-rail: #f6f8f4;
  --animate-wz-in: wz-in .3s ease;
  @keyframes wz-in { from { opacity: 0; transform: translateY(6px); } }
}
JavaScript
// Form wizard: shows one step at a time, marks the stepper with data-state and aria-current="step",
// checks the step's fields before moving on and moves focus to the new step's heading.
// Back / Continue can sit in one shared [data-nav] bar or inside every step: one delegated click handler serves both.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
  var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li'), last = panels.length - 1;
  var nav = wz.querySelector('[data-nav]'), submit = wz.querySelector('[type="submit"]'), done = wz.querySelector('[data-done]'), at = 0;
  function heading(p) { return document.getElementById(p.getAttribute('aria-labelledby')); }
  function mark(i) {
    steps.forEach(function (s, n) {
      s.dataset.state = n < i ? 'done' : n === i ? 'current' : 'next';
      if (n === i) s.setAttribute('aria-current', 'step'); else s.removeAttribute('aria-current');
      s.querySelector('[data-sr]').textContent = n < i ? ', completed' : '';
    });
  }
  function go(i, focus) {
    at = i; mark(i);
    panels.forEach(function (p, n) { p.hidden = n !== i; });
    wz.querySelectorAll('[data-back]').forEach(function (b) { b.hidden = i === 0; });
    wz.querySelectorAll('[data-next]').forEach(function (b) { b.hidden = i === last; });
    if (submit) submit.hidden = i !== last;
    wz.querySelectorAll('[data-out]').forEach(function (o) {
      var f = wz.elements[o.dataset.out], v = f && (f.length && !f.options ? [].filter.call(f, function (x) { return x.checked; }).map(function (x) { return x.value; }).join(', ') : f.value);
      o.textContent = v || 'None';
    });
    if (focus) heading(panels[i]).focus();
  }
  function valid() {
    var bad = [].find.call(panels[at].querySelectorAll('input, select, textarea'), function (f) { return !f.checkValidity(); });
    if (bad) bad.reportValidity();
    return !bad;
  }
  wz.noValidate = true;
  wz.addEventListener('click', function (e) {
    var b = e.target.closest('[data-back], [data-next], [data-restart]');
    if (!b) return;
    if (b.hasAttribute('data-back')) go(at - 1, true);
    else if (b.hasAttribute('data-next')) { if (valid()) go(at + 1, true); }
    else { wz.reset(); done.hidden = true; if (nav) nav.hidden = false; go(0, true); }
  });
  wz.addEventListener('submit', function (e) {
    e.preventDefault();
    if (!valid()) return;
    if (at < last) return go(at + 1, true);
    // Last step: send the form here (fetch or wz.submit()). The demo shows the done panel instead.
    if (!done) return;
    panels[at].hidden = true; if (nav) nav.hidden = true; mark(steps.length);
    done.hidden = false; heading(done).focus();
  });
  go(0);
});

About this set

Two vertical multi-step forms that keep every step in view. Steps expand in place is a Material-style stepper where the ordered list is the layout: each list item holds a numbered dot, the step title and the step’s fields right underneath, with Continue and Back buttons inside the open step. Finished steps collapse to a one-line summary of what was entered, such as the shop name and currency, and the connector line between the dots turns green as you go. Use it for short setup flows on a single column, like opening a shop. Side rail with descriptions puts the steps in a left column with a title and a one-line description each, and shows only the current step on the right with a shared Back and Continue bar. It suits longer applications where people want to see what is coming, and under 640px the rail turns into a compact row of dots with the current title. One small vanilla script drives both: it shows one step at a time, checks the step’s fields with the browser’s own validation before moving on, marks the stepper with aria-current and moves focus to the new step’s heading. Without JavaScript every step shows and the form submits normally.

What’s included

  • Expand-in-place stepper with per-step Continue and Back buttons and a summary line for finished steps
  • Side rail with step descriptions that collapses to a row of dots on phones
  • Native checkValidity and reportValidity per step; Enter in a field acts as Continue
  • Review slots and done panels filled from the form's own values
  • Plain CSS, Bootstrap 5.3.8 and Tailwind CSS 4 versions share one identical script

Accessibility

The stepper is an ordered list labelled Progress; the current step carries aria-current="step" and finished steps get a visually hidden ", completed". Each step is a section named by its heading, which starts with a Step 2 of 4 kicker and receives focus on Back and Continue, so screen readers announce where you are. Every control has a visible label and a visible focus style, field borders and text meet WCAG AA contrast, and transitions and the step fade are switched off under prefers-reduced-motion.

Customise it

Change the --wz-accent, --wz-accent-dark, --wz-soft and --wz-tint custom properties on .wz in the plain CSS and Bootstrap versions (Bootstrap also maps --bs-border-color there), or the --color-wz-* values in the Tailwind @theme block.