16 CSS Multi-Step Form UI07 / 16

CSS + JSMIT licensed

Event & Conference Registration

A vibrant conference registration flow — Contact → Tickets → Add-ons → Summary — with quantity steppers on ticket tiers, add-on toggle cards, and a running total that animates in the footer of every step. The total being always-visible is the trick: attendees never hit a surprise price at the end.

Published

Live Demo
Try it

The code

<section class="msf-07" aria-label="Event registration demo">
  <form class="msf-07__card" novalidate>
    <header class="msf-07__head">
      <div class="msf-07__brand"><b class="msf-07__glyph">◢◤</b><div><h3>FRONTIER CONF 2027</h3><p>Mar 12–14 · Austin, TX</p></div></div>
      <ol class="msf-07__mini"><li class="is-active" aria-current="step">Contact</li><li>Tickets</li><li>Add-ons</li><li>Summary</li></ol>
    </header>
    <fieldset class="msf-07__step">
      <h4 class="msf-07__title" tabindex="-1">Who's the primary contact?</h4>
      <div class="msf-07__grid">
        <label class="msf-07__field"><span>Full name</span><input type="text" name="name" autocomplete="name" required placeholder="Dana Whitfield"></label>
        <label class="msf-07__field"><span>Email</span><input type="email" name="email" autocomplete="email" required placeholder="dana@studio.com"></label>
        <label class="msf-07__field msf-07__field--full"><span>Company <em>(shows on badges)</em></span><input type="text" name="company" autocomplete="organization" placeholder="Studio North"></label>
      </div>
      <div class="msf-07__nav"><span></span><button type="button" class="msf-07__next">Choose tickets</button></div>
    </fieldset>
    <fieldset class="msf-07__step" hidden>
      <h4 class="msf-07__title" tabindex="-1">Pick your tickets</h4>
      <div class="msf-07__tickets">
        <div class="msf-07__ticket" data-price="349"><div><b>General</b><small>All talks + expo floor</small></div><span class="msf-07__price">$349</span>
          <div class="msf-07__qty"><button type="button" class="msf-07__minus" aria-label="Fewer General tickets">−</button><input type="text" name="qty-general" value="1" readonly aria-label="General ticket quantity"><button type="button" class="msf-07__plus" aria-label="More General tickets">+</button></div></div>
        <div class="msf-07__ticket msf-07__ticket--vip" data-price="749"><div><b>VIP <i>Popular</i></b><small>Front rows, speaker dinner, lounge</small></div><span class="msf-07__price">$749</span>
          <div class="msf-07__qty"><button type="button" class="msf-07__minus" aria-label="Fewer VIP tickets">−</button><input type="text" name="qty-vip" value="0" readonly aria-label="VIP ticket quantity"><button type="button" class="msf-07__plus" aria-label="More VIP tickets">+</button></div></div>
        <div class="msf-07__ticket" data-price="149"><div><b>Online</b><small>Live stream + recordings</small></div><span class="msf-07__price">$149</span>
          <div class="msf-07__qty"><button type="button" class="msf-07__minus" aria-label="Fewer Online tickets">−</button><input type="text" name="qty-online" value="0" readonly aria-label="Online ticket quantity"><button type="button" class="msf-07__plus" aria-label="More Online tickets">+</button></div></div>
      </div>
      <div class="msf-07__nav"><button type="button" class="msf-07__back">Back</button><button type="button" class="msf-07__next">Add-ons</button></div>
    </fieldset>
    <fieldset class="msf-07__step" hidden>
      <h4 class="msf-07__title" tabindex="-1">Make it a trip</h4>
      <div class="msf-07__addons">
        <label class="msf-07__addon" data-price="89"><input type="checkbox" name="addon" value="Workshop day"><div><b>Workshop day</b><small>Hands-on, day 0</small></div><span>+$89</span></label>
        <label class="msf-07__addon" data-price="45"><input type="checkbox" name="addon" value="After-party"><div><b>After-party</b><small>Rooftop, day 2</small></div><span>+$45</span></label>
        <label class="msf-07__addon" data-price="25"><input type="checkbox" name="addon" value="Swag kit"><div><b>Swag kit</b><small>Tee, cap, stickers</small></div><span>+$25</span></label>
      </div>
      <div class="msf-07__nav"><button type="button" class="msf-07__back">Back</button><button type="button" class="msf-07__next">Review order</button></div>
    </fieldset>
    <fieldset class="msf-07__step" hidden>
      <h4 class="msf-07__title" tabindex="-1">Your order</h4>
      <ul class="msf-07__lines"></ul>
      <div class="msf-07__nav"><button type="button" class="msf-07__back">Back</button><button type="button" class="msf-07__next msf-07__next--go">Pay &amp; register</button></div>
    </fieldset>
    <div class="msf-07__done" hidden><b>◢◤</b><h4 tabindex="-1">See you in Austin!</h4><p>Tickets and calendar holds are in your inbox.</p></div>
    <footer class="msf-07__foot"><span>Order total</span><b class="msf-07__total" aria-live="polite">$349</b></footer>
  </form>
</section>
.msf-07,
.msf-07 *,
.msf-07 *::before,
.msf-07 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.msf-07 {
  width: 100%;
  min-height: 100vh;
  --pop: oklch(0.62 0.24 305);
  --pop2: oklch(0.7 0.19 40);
  --bg: oklch(0.15 0.02 300);
  --panel: oklch(0.19 0.022 300);
  --line: oklch(0.3 0.025 300);
  --txt: oklch(0.94 0.005 300);
  --mut: oklch(0.66 0.02 300);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--txt);
  padding: 48px 20px;
  background: radial-gradient(90% 120% at 15% 0%,oklch(0.24 0.07 305/.8),transparent 60%),radial-gradient(80% 100% at 100% 100%,oklch(0.22 0.06 40/.7),transparent 55%),var(--bg);
  display: grid;
  place-items: center;
}

.msf-07__card {
  width: min(620px,100%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 40px 90px -40px rgb(0 0 0/.7);
}

.msf-07__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 22px 28px;
  border-bottom: 1px solid var(--line);
}

.msf-07__brand {
  display: flex;
  gap: 12px;
  align-items: center;
}

.msf-07__glyph {
  font-size: 20px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.msf-07__brand h3 {
  font-size: 15px;
  letter-spacing: .06em;
}

.msf-07__brand p {
  font-size: 12px;
  color: var(--mut);
}

.msf-07__mini {
  display: flex;
  gap: 14px;
  list-style: none;
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--mut);
}

.msf-07__mini li {
  position: relative;
  padding-bottom: 4px;
}

.msf-07__mini li.is-active {
  color: var(--txt);
}

.msf-07__mini li.is-active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
}

.msf-07__mini li.is-done {
  color: var(--pop);
}

.msf-07__step {
  border: none;
  padding: 26px 28px;
}

.msf-07__step:not([hidden]) {
  animation: msf-07-in .35s cubic-bezier(.2,.8,.2,1);
}

.msf-07__title {
  font-size: 20px;
  letter-spacing: -.01em;
  margin-bottom: 18px;
  outline: none;
}

.msf-07__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

@media (max-width: 520px) {
  .msf-07__grid {
    grid-template-columns: 1fr;
  }
}

.msf-07__field {
  display: grid;
  gap: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--mut);
}

.msf-07__field em {
  font-style: normal;
  font-weight: 400;
}

.msf-07__field--full {
  grid-column: 1/-1;
}

.msf-07__field input {
  font: inherit;
  font-weight: 500;
  color: var(--txt);
  padding: 11px 13px;
  background: oklch(0.15 0.02 300);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  transition: border-color .2s,box-shadow .2s;
}

.msf-07__field input:focus-visible {
  outline: none;
  border-color: var(--pop);
  box-shadow: 0 0 0 4px color-mix(in oklab,var(--pop) 20%,transparent);
}

.msf-07__field input[aria-invalid="true"] {
  border-color: oklch(0.62 0.2 25);
}

.msf-07__err {
  grid-column: 1/-1;
  font-size: 12px;
  font-weight: 700;
  color: oklch(0.74 0.15 25);
}

.msf-07__tickets {
  display: grid;
  gap: 10px;
}

.msf-07__ticket {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 14px;
  align-items: center;
  padding: 16px 18px;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  background: oklch(0.165 0.02 300);
}

.msf-07__ticket--vip {
  border-color: color-mix(in oklab,var(--pop) 55%,var(--line));
  background: linear-gradient(120deg,color-mix(in oklab,var(--pop) 12%,transparent),transparent 60%),oklch(0.165 0.02 300);
}

.msf-07__ticket b {
  font-size: 15px;
  display: flex;
  gap: 8px;
  align-items: center;
}

.msf-07__ticket b i {
  font-style: normal;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 99px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  color: #fff;
}

.msf-07__ticket small {
  font-size: 12px;
  color: var(--mut);
}

.msf-07__price {
  font-size: 16px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.msf-07__qty {
  display: flex;
  align-items: center;
  gap: 2px;
  border: 1.5px solid var(--line);
  border-radius: 99px;
  padding: 3px;
}

.msf-07__qty button {
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--txt);
  font-size: 16px;
  cursor: pointer;
  transition: background .15s;
}

.msf-07__qty button:hover {
  background: var(--line);
}

.msf-07__qty button:focus-visible {
  outline: 2px solid var(--pop);
  outline-offset: 1px;
}

.msf-07__qty input {
  width: 30px;
  text-align: center;
  font: inherit;
  font-weight: 800;
  color: var(--txt);
  background: none;
  border: none;
  font-variant-numeric: tabular-nums;
}

.msf-07__addons {
  display: grid;
  gap: 10px;
}

.msf-07__addon {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 15px 18px;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  cursor: pointer;
  background: oklch(0.165 0.02 300);
  transition: border-color .2s,background .2s;
}

.msf-07__addon input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  clip-path: inset(50%);
}

.msf-07__addon b {
  font-size: 14.5px;
  display: block;
}

.msf-07__addon small {
  font-size: 12px;
  color: var(--mut);
}

.msf-07__addon>span {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--mut);
}

.msf-07__addon:hover {
  border-color: color-mix(in oklab,var(--pop) 45%,var(--line));
}

.msf-07__addon:has(:checked) {
  border-color: var(--pop);
  background: color-mix(in oklab,var(--pop) 10%,oklch(0.165 0.02 300));
}

.msf-07__addon:has(:checked)>span {
  color: var(--pop);
}

.msf-07__addon:has(:checked) b::after {
  content: " ✓";
  color: var(--pop);
}

.msf-07__addon:has(:focus-visible) {
  outline: 3px solid var(--pop);
  outline-offset: 2px;
}

.msf-07__lines {
  list-style: none;
  display: grid;
  gap: 8px;
  font-size: 14px;
}

.msf-07__lines li {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  padding: 11px 14px;
  background: oklch(0.165 0.02 300);
  border-radius: 10px;
  font-variant-numeric: tabular-nums;
}

.msf-07__lines li.is-total {
  border: 1.5px solid color-mix(in oklab,var(--pop) 45%,var(--line));
  font-weight: 800;
  font-size: 15px;
}

.msf-07__nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 22px;
}

.msf-07__next,
.msf-07__back {
  font: inherit;
  font-weight: 800;
  cursor: pointer;
  border-radius: 999px;
  padding: 12px 24px;
  transition: transform .15s,filter .2s;
}

.msf-07__next {
  border: none;
  color: #fff;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  box-shadow: 0 12px 30px -12px var(--pop);
}

.msf-07__next:hover {
  transform: translateY(-1px);
  filter: brightness(1.1);
}

.msf-07__next:disabled {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

.msf-07__back {
  border: 1.5px solid var(--line);
  background: none;
  color: var(--mut);
}

.msf-07__back:hover {
  color: var(--txt);
}

.msf-07__next:focus-visible,
.msf-07__back:focus-visible {
  outline: 3px solid var(--pop);
  outline-offset: 2px;
}

.msf-07__done[hidden] {
  display: none;
}

.msf-07__done {
  display: grid;
  justify-items: center;
  gap: 8px;
  text-align: center;
  padding: 36px 0;
}

.msf-07__done b {
  font-size: 30px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: msf-07-pop .5s cubic-bezier(.34,1.56,.64,1);
}

.msf-07__done p {
  color: var(--mut);
  font-size: 14px;
}

.msf-07__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 28px;
  border-top: 1px dashed var(--line);
  background: oklch(0.165 0.02 300);
  font-size: 13px;
  color: var(--mut);
}

.msf-07__total {
  font-size: 20px;
  font-weight: 800;
  color: var(--txt);
  font-variant-numeric: tabular-nums;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@keyframes msf-07-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes msf-07-pop {
  from {
    transform: scale(.4) rotate(-8deg);
  }

  to {
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .msf-07 * {
    animation: none!important;
    transition-duration: .01ms!important;
  }
}
(() => {
  const root = document.querySelector('.msf-07'); if (!root) return;
  const form = root.querySelector('.msf-07__card');
  const steps = [...root.querySelectorAll('.msf-07__step')];
  const mini = [...root.querySelectorAll('.msf-07__mini li')];
  const total = root.querySelector('.msf-07__total');
  let cur = 0;
  const fmt = n => '$' + n.toLocaleString('en-US');
  const order = () => {
    const lines = [];
    root.querySelectorAll('.msf-07__ticket').forEach(t => {
      const q = +t.querySelector('input').value;
      if (q > 0) lines.push([q + '\u00d7 ' + t.querySelector('b').firstChild.textContent.trim(), q * +t.dataset.price]);
    });
    root.querySelectorAll('.msf-07__addon:has(:checked)').forEach(a => lines.push([a.querySelector('b').textContent.replace(' \u2713',''), +a.dataset.price]));
    return lines;
  };
  const recalc = () => {
    const lines = order();
    const sum = lines.reduce((s,[,p]) => s+p, 0);
    total.textContent = fmt(sum);
    const pay = root.querySelector('.msf-07__next--go'); if (pay) pay.disabled = sum === 0;
    return {lines, sum};
  };
  const validate = (fs) => {
    let ok = true;
    fs.querySelectorAll('.msf-07__err').forEach(e => e.remove());
    if (fs.querySelector('.msf-07__tickets') && recalc().sum === 0) {
      const err = document.createElement('p'); err.className='msf-07__err'; err.setAttribute('role','alert');
      err.textContent = 'Add at least one ticket to continue.';
      fs.querySelector('.msf-07__tickets').after(err); return false;
    }
    fs.querySelectorAll('input[required]').forEach(el => {
      const bad = !el.checkValidity(); el.setAttribute('aria-invalid', bad);
      if (bad && ok) { ok = false;
        const err = document.createElement('p'); err.className='msf-07__err'; err.setAttribute('role','alert');
        err.textContent = 'Please complete the highlighted fields.';
        el.closest('.msf-07__grid').append(err); el.focus();
      }
    });
    return ok;
  };
  const show = (i) => {
    cur = i;
    steps.forEach((s,k) => s.hidden = k!==i);
    mini.forEach((m,k) => {
      m.classList.toggle('is-active',k===i); m.classList.toggle('is-done',k<i);
      k===i ? m.setAttribute('aria-current','step') : m.removeAttribute('aria-current');
    });
    if (i === steps.length-1) {
      const {lines, sum} = recalc();
      root.querySelector('.msf-07__lines').innerHTML =
        lines.map(([l,p]) => '<li><span>'+l+'</span><span>'+fmt(p)+'</span></li>').join('') +
        '<li class="is-total"><span>Total</span><span>'+fmt(sum)+'</span></li>';
    }
    steps[i].querySelector('.msf-07__title').focus();
  };
  root.addEventListener('click', e => {
    const minus = e.target.closest('.msf-07__minus'), plus = e.target.closest('.msf-07__plus');
    if (minus || plus) {
      const inp = (minus||plus).closest('.msf-07__qty').querySelector('input');
      inp.value = Math.min(10, Math.max(0, +inp.value + (plus ? 1 : -1)));
      recalc(); return;
    }
    if (e.target.closest('.msf-07__next:not(.msf-07__next--go):not(.msf-07__submit):not(.msf-07__skip)')) { if (validate(steps[cur])) show(cur+1); }
    if (e.target.closest('.msf-07__back')) show(cur-1);
  });
  root.addEventListener('change', e => { if (e.target.matches('.msf-07__addon input')) recalc(); });
  root.addEventListener('click', (e) => {
    if (!e.target.closest('.msf-07__next--go, .msf-07__submit, .msf-07__skip')) return;

    e.preventDefault();
    steps.forEach(s => s.hidden = true);
    mini.forEach(m => { m.classList.add('is-done'); m.classList.remove('is-active'); });
    const done = root.querySelector('.msf-07__done'); done.hidden = false; done.querySelector('h4').focus();
    });
  recalc();
})();
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS Multi-Step Form UI from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Event & Conference Registration
Source: https://codefronts.com/components/css-multi-step-form/event-and-conference-registration/

A vibrant conference registration flow — Contact → Tickets → Add-ons → Summary — with quantity steppers on ticket tiers, add-on toggle cards, and a running total that animates in the footer of every step. The total being always-visible is the trick: attendees never hit a surprise price at the end.
## HTML
```html
<section class="msf-07" aria-label="Event registration demo">
  <form class="msf-07__card" novalidate>
    <header class="msf-07__head">
      <div class="msf-07__brand"><b class="msf-07__glyph">◢◤</b><div><h3>FRONTIER CONF 2027</h3><p>Mar 12–14 · Austin, TX</p></div></div>
      <ol class="msf-07__mini"><li class="is-active" aria-current="step">Contact</li><li>Tickets</li><li>Add-ons</li><li>Summary</li></ol>
    </header>
    <fieldset class="msf-07__step">
      <h4 class="msf-07__title" tabindex="-1">Who's the primary contact?</h4>
      <div class="msf-07__grid">
        <label class="msf-07__field"><span>Full name</span><input type="text" name="name" autocomplete="name" required placeholder="Dana Whitfield"></label>
        <label class="msf-07__field"><span>Email</span><input type="email" name="email" autocomplete="email" required placeholder="dana@studio.com"></label>
        <label class="msf-07__field msf-07__field--full"><span>Company <em>(shows on badges)</em></span><input type="text" name="company" autocomplete="organization" placeholder="Studio North"></label>
      </div>
      <div class="msf-07__nav"><span></span><button type="button" class="msf-07__next">Choose tickets</button></div>
    </fieldset>
    <fieldset class="msf-07__step" hidden>
      <h4 class="msf-07__title" tabindex="-1">Pick your tickets</h4>
      <div class="msf-07__tickets">
        <div class="msf-07__ticket" data-price="349"><div><b>General</b><small>All talks + expo floor</small></div><span class="msf-07__price">$349</span>
          <div class="msf-07__qty"><button type="button" class="msf-07__minus" aria-label="Fewer General tickets">−</button><input type="text" name="qty-general" value="1" readonly aria-label="General ticket quantity"><button type="button" class="msf-07__plus" aria-label="More General tickets">+</button></div></div>
        <div class="msf-07__ticket msf-07__ticket--vip" data-price="749"><div><b>VIP <i>Popular</i></b><small>Front rows, speaker dinner, lounge</small></div><span class="msf-07__price">$749</span>
          <div class="msf-07__qty"><button type="button" class="msf-07__minus" aria-label="Fewer VIP tickets">−</button><input type="text" name="qty-vip" value="0" readonly aria-label="VIP ticket quantity"><button type="button" class="msf-07__plus" aria-label="More VIP tickets">+</button></div></div>
        <div class="msf-07__ticket" data-price="149"><div><b>Online</b><small>Live stream + recordings</small></div><span class="msf-07__price">$149</span>
          <div class="msf-07__qty"><button type="button" class="msf-07__minus" aria-label="Fewer Online tickets">−</button><input type="text" name="qty-online" value="0" readonly aria-label="Online ticket quantity"><button type="button" class="msf-07__plus" aria-label="More Online tickets">+</button></div></div>
      </div>
      <div class="msf-07__nav"><button type="button" class="msf-07__back">Back</button><button type="button" class="msf-07__next">Add-ons</button></div>
    </fieldset>
    <fieldset class="msf-07__step" hidden>
      <h4 class="msf-07__title" tabindex="-1">Make it a trip</h4>
      <div class="msf-07__addons">
        <label class="msf-07__addon" data-price="89"><input type="checkbox" name="addon" value="Workshop day"><div><b>Workshop day</b><small>Hands-on, day 0</small></div><span>+$89</span></label>
        <label class="msf-07__addon" data-price="45"><input type="checkbox" name="addon" value="After-party"><div><b>After-party</b><small>Rooftop, day 2</small></div><span>+$45</span></label>
        <label class="msf-07__addon" data-price="25"><input type="checkbox" name="addon" value="Swag kit"><div><b>Swag kit</b><small>Tee, cap, stickers</small></div><span>+$25</span></label>
      </div>
      <div class="msf-07__nav"><button type="button" class="msf-07__back">Back</button><button type="button" class="msf-07__next">Review order</button></div>
    </fieldset>
    <fieldset class="msf-07__step" hidden>
      <h4 class="msf-07__title" tabindex="-1">Your order</h4>
      <ul class="msf-07__lines"></ul>
      <div class="msf-07__nav"><button type="button" class="msf-07__back">Back</button><button type="button" class="msf-07__next msf-07__next--go">Pay &amp; register</button></div>
    </fieldset>
    <div class="msf-07__done" hidden><b>◢◤</b><h4 tabindex="-1">See you in Austin!</h4><p>Tickets and calendar holds are in your inbox.</p></div>
    <footer class="msf-07__foot"><span>Order total</span><b class="msf-07__total" aria-live="polite">$349</b></footer>
  </form>
</section>
```
## CSS
```css
.msf-07,
.msf-07 *,
.msf-07 *::before,
.msf-07 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.msf-07 {
  width: 100%;
  min-height: 100vh;
  --pop: oklch(0.62 0.24 305);
  --pop2: oklch(0.7 0.19 40);
  --bg: oklch(0.15 0.02 300);
  --panel: oklch(0.19 0.022 300);
  --line: oklch(0.3 0.025 300);
  --txt: oklch(0.94 0.005 300);
  --mut: oklch(0.66 0.02 300);
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--txt);
  padding: 48px 20px;
  background: radial-gradient(90% 120% at 15% 0%,oklch(0.24 0.07 305/.8),transparent 60%),radial-gradient(80% 100% at 100% 100%,oklch(0.22 0.06 40/.7),transparent 55%),var(--bg);
  display: grid;
  place-items: center;
}

.msf-07__card {
  width: min(620px,100%);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 40px 90px -40px rgb(0 0 0/.7);
}

.msf-07__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 22px 28px;
  border-bottom: 1px solid var(--line);
}

.msf-07__brand {
  display: flex;
  gap: 12px;
  align-items: center;
}

.msf-07__glyph {
  font-size: 20px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.msf-07__brand h3 {
  font-size: 15px;
  letter-spacing: .06em;
}

.msf-07__brand p {
  font-size: 12px;
  color: var(--mut);
}

.msf-07__mini {
  display: flex;
  gap: 14px;
  list-style: none;
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--mut);
}

.msf-07__mini li {
  position: relative;
  padding-bottom: 4px;
}

.msf-07__mini li.is-active {
  color: var(--txt);
}

.msf-07__mini li.is-active::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
}

.msf-07__mini li.is-done {
  color: var(--pop);
}

.msf-07__step {
  border: none;
  padding: 26px 28px;
}

.msf-07__step:not([hidden]) {
  animation: msf-07-in .35s cubic-bezier(.2,.8,.2,1);
}

.msf-07__title {
  font-size: 20px;
  letter-spacing: -.01em;
  margin-bottom: 18px;
  outline: none;
}

.msf-07__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}

@media (max-width: 520px) {
  .msf-07__grid {
    grid-template-columns: 1fr;
  }
}

.msf-07__field {
  display: grid;
  gap: 6px;
  font-size: 12px;
  font-weight: 700;
  color: var(--mut);
}

.msf-07__field em {
  font-style: normal;
  font-weight: 400;
}

.msf-07__field--full {
  grid-column: 1/-1;
}

.msf-07__field input {
  font: inherit;
  font-weight: 500;
  color: var(--txt);
  padding: 11px 13px;
  background: oklch(0.15 0.02 300);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  transition: border-color .2s,box-shadow .2s;
}

.msf-07__field input:focus-visible {
  outline: none;
  border-color: var(--pop);
  box-shadow: 0 0 0 4px color-mix(in oklab,var(--pop) 20%,transparent);
}

.msf-07__field input[aria-invalid="true"] {
  border-color: oklch(0.62 0.2 25);
}

.msf-07__err {
  grid-column: 1/-1;
  font-size: 12px;
  font-weight: 700;
  color: oklch(0.74 0.15 25);
}

.msf-07__tickets {
  display: grid;
  gap: 10px;
}

.msf-07__ticket {
  display: grid;
  grid-template-columns: 1fr auto auto;
  gap: 14px;
  align-items: center;
  padding: 16px 18px;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  background: oklch(0.165 0.02 300);
}

.msf-07__ticket--vip {
  border-color: color-mix(in oklab,var(--pop) 55%,var(--line));
  background: linear-gradient(120deg,color-mix(in oklab,var(--pop) 12%,transparent),transparent 60%),oklch(0.165 0.02 300);
}

.msf-07__ticket b {
  font-size: 15px;
  display: flex;
  gap: 8px;
  align-items: center;
}

.msf-07__ticket b i {
  font-style: normal;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .08em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 99px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  color: #fff;
}

.msf-07__ticket small {
  font-size: 12px;
  color: var(--mut);
}

.msf-07__price {
  font-size: 16px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.msf-07__qty {
  display: flex;
  align-items: center;
  gap: 2px;
  border: 1.5px solid var(--line);
  border-radius: 99px;
  padding: 3px;
}

.msf-07__qty button {
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 50%;
  background: none;
  color: var(--txt);
  font-size: 16px;
  cursor: pointer;
  transition: background .15s;
}

.msf-07__qty button:hover {
  background: var(--line);
}

.msf-07__qty button:focus-visible {
  outline: 2px solid var(--pop);
  outline-offset: 1px;
}

.msf-07__qty input {
  width: 30px;
  text-align: center;
  font: inherit;
  font-weight: 800;
  color: var(--txt);
  background: none;
  border: none;
  font-variant-numeric: tabular-nums;
}

.msf-07__addons {
  display: grid;
  gap: 10px;
}

.msf-07__addon {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 12px;
  align-items: center;
  padding: 15px 18px;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  cursor: pointer;
  background: oklch(0.165 0.02 300);
  transition: border-color .2s,background .2s;
}

.msf-07__addon input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  clip-path: inset(50%);
}

.msf-07__addon b {
  font-size: 14.5px;
  display: block;
}

.msf-07__addon small {
  font-size: 12px;
  color: var(--mut);
}

.msf-07__addon>span {
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--mut);
}

.msf-07__addon:hover {
  border-color: color-mix(in oklab,var(--pop) 45%,var(--line));
}

.msf-07__addon:has(:checked) {
  border-color: var(--pop);
  background: color-mix(in oklab,var(--pop) 10%,oklch(0.165 0.02 300));
}

.msf-07__addon:has(:checked)>span {
  color: var(--pop);
}

.msf-07__addon:has(:checked) b::after {
  content: " ✓";
  color: var(--pop);
}

.msf-07__addon:has(:focus-visible) {
  outline: 3px solid var(--pop);
  outline-offset: 2px;
}

.msf-07__lines {
  list-style: none;
  display: grid;
  gap: 8px;
  font-size: 14px;
}

.msf-07__lines li {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  padding: 11px 14px;
  background: oklch(0.165 0.02 300);
  border-radius: 10px;
  font-variant-numeric: tabular-nums;
}

.msf-07__lines li.is-total {
  border: 1.5px solid color-mix(in oklab,var(--pop) 45%,var(--line));
  font-weight: 800;
  font-size: 15px;
}

.msf-07__nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 22px;
}

.msf-07__next,
.msf-07__back {
  font: inherit;
  font-weight: 800;
  cursor: pointer;
  border-radius: 999px;
  padding: 12px 24px;
  transition: transform .15s,filter .2s;
}

.msf-07__next {
  border: none;
  color: #fff;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  box-shadow: 0 12px 30px -12px var(--pop);
}

.msf-07__next:hover {
  transform: translateY(-1px);
  filter: brightness(1.1);
}

.msf-07__next:disabled {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

.msf-07__back {
  border: 1.5px solid var(--line);
  background: none;
  color: var(--mut);
}

.msf-07__back:hover {
  color: var(--txt);
}

.msf-07__next:focus-visible,
.msf-07__back:focus-visible {
  outline: 3px solid var(--pop);
  outline-offset: 2px;
}

.msf-07__done[hidden] {
  display: none;
}

.msf-07__done {
  display: grid;
  justify-items: center;
  gap: 8px;
  text-align: center;
  padding: 36px 0;
}

.msf-07__done b {
  font-size: 30px;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: msf-07-pop .5s cubic-bezier(.34,1.56,.64,1);
}

.msf-07__done p {
  color: var(--mut);
  font-size: 14px;
}

.msf-07__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 28px;
  border-top: 1px dashed var(--line);
  background: oklch(0.165 0.02 300);
  font-size: 13px;
  color: var(--mut);
}

.msf-07__total {
  font-size: 20px;
  font-weight: 800;
  color: var(--txt);
  font-variant-numeric: tabular-nums;
  background: linear-gradient(90deg,var(--pop),var(--pop2));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@keyframes msf-07-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes msf-07-pop {
  from {
    transform: scale(.4) rotate(-8deg);
  }

  to {
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .msf-07 * {
    animation: none!important;
    transition-duration: .01ms!important;
  }
}
```

## JavaScript
```js
(() => {
  const root = document.querySelector('.msf-07'); if (!root) return;
  const form = root.querySelector('.msf-07__card');
  const steps = [...root.querySelectorAll('.msf-07__step')];
  const mini = [...root.querySelectorAll('.msf-07__mini li')];
  const total = root.querySelector('.msf-07__total');
  let cur = 0;
  const fmt = n => '$' + n.toLocaleString('en-US');
  const order = () => {
    const lines = [];
    root.querySelectorAll('.msf-07__ticket').forEach(t => {
      const q = +t.querySelector('input').value;
      if (q > 0) lines.push([q + '\u00d7 ' + t.querySelector('b').firstChild.textContent.trim(), q * +t.dataset.price]);
    });
    root.querySelectorAll('.msf-07__addon:has(:checked)').forEach(a => lines.push([a.querySelector('b').textContent.replace(' \u2713',''), +a.dataset.price]));
    return lines;
  };
  const recalc = () => {
    const lines = order();
    const sum = lines.reduce((s,[,p]) => s+p, 0);
    total.textContent = fmt(sum);
    const pay = root.querySelector('.msf-07__next--go'); if (pay) pay.disabled = sum === 0;
    return {lines, sum};
  };
  const validate = (fs) => {
    let ok = true;
    fs.querySelectorAll('.msf-07__err').forEach(e => e.remove());
    if (fs.querySelector('.msf-07__tickets') && recalc().sum === 0) {
      const err = document.createElement('p'); err.className='msf-07__err'; err.setAttribute('role','alert');
      err.textContent = 'Add at least one ticket to continue.';
      fs.querySelector('.msf-07__tickets').after(err); return false;
    }
    fs.querySelectorAll('input[required]').forEach(el => {
      const bad = !el.checkValidity(); el.setAttribute('aria-invalid', bad);
      if (bad && ok) { ok = false;
        const err = document.createElement('p'); err.className='msf-07__err'; err.setAttribute('role','alert');
        err.textContent = 'Please complete the highlighted fields.';
        el.closest('.msf-07__grid').append(err); el.focus();
      }
    });
    return ok;
  };
  const show = (i) => {
    cur = i;
    steps.forEach((s,k) => s.hidden = k!==i);
    mini.forEach((m,k) => {
      m.classList.toggle('is-active',k===i); m.classList.toggle('is-done',k<i);
      k===i ? m.setAttribute('aria-current','step') : m.removeAttribute('aria-current');
    });
    if (i === steps.length-1) {
      const {lines, sum} = recalc();
      root.querySelector('.msf-07__lines').innerHTML =
        lines.map(([l,p]) => '<li><span>'+l+'</span><span>'+fmt(p)+'</span></li>').join('') +
        '<li class="is-total"><span>Total</span><span>'+fmt(sum)+'</span></li>';
    }
    steps[i].querySelector('.msf-07__title').focus();
  };
  root.addEventListener('click', e => {
    const minus = e.target.closest('.msf-07__minus'), plus = e.target.closest('.msf-07__plus');
    if (minus || plus) {
      const inp = (minus||plus).closest('.msf-07__qty').querySelector('input');
      inp.value = Math.min(10, Math.max(0, +inp.value + (plus ? 1 : -1)));
      recalc(); return;
    }
    if (e.target.closest('.msf-07__next:not(.msf-07__next--go):not(.msf-07__submit):not(.msf-07__skip)')) { if (validate(steps[cur])) show(cur+1); }
    if (e.target.closest('.msf-07__back')) show(cur-1);
  });
  root.addEventListener('change', e => { if (e.target.matches('.msf-07__addon input')) recalc(); });
  root.addEventListener('click', (e) => {
    if (!e.target.closest('.msf-07__next--go, .msf-07__submit, .msf-07__skip')) return;

    e.preventDefault();
    steps.forEach(s => s.hidden = true);
    mini.forEach(m => { m.classList.add('is-done'); m.classList.remove('is-active'); });
    const done = root.querySelector('.msf-07__done'); done.hidden = false; done.querySelector('h4').focus();
    });
  recalc();
})();
```

How this works

Ticket tiers pair a price card with an accessible quantity stepper: −/+ buttons flank a readonly <input> whose value the buttons mutate, announced by a shared aria-live total in the sticky footer. Add-ons are checkbox cards with their own prices; every change recomputes the order from DOM state — one recalc() is the single source of truth.

The final step renders a line-item summary from the same calculation, so summary and footer can never disagree. Gradient accents and a ticket-stub visual give it event energy while the form bones stay strictly semantic.

Make it yours

  • Prices live in data-price attributes — change them without touching JS.
  • Cap ticket quantities via the stepper's max logic (default 10).
  • Add a promo-code field that applies a multiplier inside recalc().
  • Tone the gradient down to a solid for corporate events via --pop.

Gotchas — read before shipping

  • Quantity steppers need aria-labels on the −/+ buttons ('Fewer General tickets') — a bare '−' is meaningless to a screen reader.
  • Recompute totals from the DOM, never increment a running JS number — the two drift the moment a user goes back a step.
  • Disable checkout at qty 0 with a real disabled attribute, and explain why in the footer text.

Browser support

ChromeSafariFirefoxEdge
111+16.2+121+111+

Floor set by :has(), oklch(), color-mix() as this demo is written. The core technique itself goes back further — Chrome 105+.

Gradient text uses background-clip:text (universal); :has() for checked cards. All math is DOM-read JS — no fragile CSS counters.

Search CodeFronts

Loading…