/* Checkout wizards. A: a static progress trail for a checkout split over several pages (no script) in two
styles, chevrons and a slim numbered line. B: a single-page checkout wizard with an order summary beside it.
Its script shows one step at a time, sets data-state and aria-current="step" on the stepper, moves focus to the
new step's heading, keeps shipping and totals in line with the delivery choice and shows only the card's last
four digits in the review. Without JavaScript every step shows and the Pay button submits the form normally. */
.ck,.ck-prog{--ck-accent:#c2410c;--ck-dark:#9a3412;--ck-soft:#ffedd5;--ck-tint:#fff7ed;--ck-ink:#1c1917;--ck-muted:#57534e;
--ck-field:#8f867e;--ck-track:#e2dcd4;--ck-rule:#e7e2da;--ck-stone:#efebe5;color:var(--ck-ink)}
.ck [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}
.ck-card{background:#fff;border:1px solid var(--ck-rule);border-radius:16px;box-shadow:0 1px 2px rgba(28,25,23,.05),0 12px 28px -18px rgba(124,45,18,.22)}
.ck-secure{display:inline-flex;align-items:center;gap:6px;font-size:.82rem;color:var(--ck-muted)}
.ck-secure svg,.ck-btn svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.ck-tick{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;flex:none}
/* A1 chevrons: the arrow shape is a clip-path on ::before, so focus outlines on the links are never clipped */
.ck-prog{display:grid;gap:16px}
.ck-top{padding:22px 26px 26px}
.ck-brand{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:18px}
.ck-brand b{display:inline-flex;align-items:center;gap:9px;font-size:1.05rem;letter-spacing:-.01em}
.ck-brand b::before{content:"";width:22px;height:22px;border-radius:7px;background:var(--ck-accent);box-shadow:inset -6px -6px 0 var(--ck-dark)}
.ck-chevs ol{list-style:none;margin:0;padding:0;display:flex}
.ck-chevs li{position:relative;isolation:isolate;flex:1;min-width:0;display:flex;height:50px;padding:0 14px;font-size:.92rem;color:var(--ck-muted)}
.ck-chevs li + li{margin-left:-10px}
.ck-chevs li::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--ck-stone);transition:background .2s;
clip-path:polygon(0 0,calc(100% - 14px) 0,100% 50%,calc(100% - 14px) 100%,0 100%,14px 50%)}
.ck-chevs li:first-child{padding-left:0}
.ck-chevs li:first-child::before{border-radius:10px 0 0 10px;clip-path:polygon(0 0,calc(100% - 14px) 0,100% 50%,calc(100% - 14px) 100%,0 100%)}
.ck-chevs li:last-child{padding-right:0}
.ck-chevs li:last-child::before{border-radius:0 10px 10px 0;clip-path:polygon(0 0,100% 0,100% 100%,0 100%,14px 50%)}
.ck-chevs a,.ck-chevs .ck-in{flex:1;min-width:0;display:flex;align-items:center;justify-content:center;gap:8px;color:inherit;text-decoration:none;border-radius:6px;white-space:nowrap}
.ck-chevs a:hover .ck-name{text-decoration:underline;text-underline-offset:3px}
.ck-chevs a:focus-visible{outline:2px solid var(--ck-ink);outline-offset:-4px}
.ck-chevs [data-state="done"]{color:var(--ck-dark);font-weight:600}
.ck-chevs [data-state="done"]::before{background:var(--ck-soft)}
.ck-chevs [data-state="done"]:hover::before{background:#fed7aa}
.ck-chevs [data-state="current"]{color:#fff;font-weight:600}
.ck-chevs [data-state="current"]::before{background:var(--ck-accent)}
.ck-n{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;border:1.5px solid currentColor;font-size:.76rem;font-weight:700;flex:none}
@media (max-width:560px){.ck-top{padding:18px 16px 20px}
.ck-chevs li{flex:none;width:60px;height:46px}.ck-chevs li:first-child{width:50px}.ck-chevs [data-state="current"]{flex:1;width:auto}
.ck-chevs li:not([data-state="current"]) .ck-name{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}}
/* A2 slim line: small numbered circles, done steps are links, separators are hidden from screen readers */
.ck-slim{padding:14px 22px}
.ck-line ol{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;font-size:.9rem;color:var(--ck-muted)}
.ck-line li{display:flex;align-items:center;gap:8px}
.ck-sep{color:#78716c;margin-left:4px;font-weight:400}
.ck-line a{display:inline-flex;align-items:center;gap:8px;color:var(--ck-dark);font-weight:600;text-decoration:underline;text-decoration-color:rgba(154,52,18,.35);text-underline-offset:4px;border-radius:4px}
.ck-line a:hover{text-decoration-color:currentColor}
.ck-line a:focus-visible{outline:2px solid var(--ck-accent);outline-offset:3px}
.ck-c{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;border:1.5px solid currentColor;font-size:.72rem;font-weight:700;flex:none}
.ck-c .ck-tick{width:13px;height:13px}
.ck-line [data-state="done"] .ck-c{background:var(--ck-soft);border-color:var(--ck-soft)}
.ck-line [data-state="current"]{color:var(--ck-ink);font-weight:600}
.ck-line [data-state="current"] .ck-c{background:var(--ck-accent);border-color:var(--ck-accent);color:#fff}
@media (max-width:560px){.ck-slim{padding:12px 16px}.ck-line ol{gap:6px 8px;font-size:.84rem}.ck-line li{gap:6px}.ck-sep{margin-left:2px}.ck-c{width:20px;height:20px}}
/* B layout: form card and order summary side by side, stacked below 992px */
.ck{display:grid;gap:24px;align-items:start}
@media (min-width:992px){.ck{grid-template-columns:2fr 1fr}.ck-sum{position:sticky;top:20px}}
.ck-main{padding:28px 32px;min-width:0}
.ck-head{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.ck-title{font-size:1.35rem;font-weight:700;letter-spacing:-.015em;margin:0}
/* stepper: zero-padded numbers over a thin track that fills to half on the current step and fully when done */
.ck-steps{list-style:none;margin:22px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.ck-steps li{position:relative;display:flex;align-items:center;gap:10px;padding-bottom:12px;font-size:.9rem;color:var(--ck-muted);min-width:0}
.ck-steps li::after,.ck-steps li::before{content:"";position:absolute;left:0;bottom:0;height:3px;border-radius:3px}
.ck-steps li::after{right:0;background:var(--ck-track)}
.ck-steps li::before{width:0;z-index:1;background:var(--ck-accent);transition:width .35s ease}
.ck-num{display:grid;place-items:center;min-width:28px;height:26px;padding:0 4px;border-radius:7px;background:var(--ck-stone);font-size:.78rem;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.03em;transition:background .25s,color .25s}
.ck-num svg{display:none;width:15px;height:15px}
.ck-steps [data-state="current"]{color:var(--ck-ink);font-weight:600}
.ck-steps [data-state="current"]::before{width:50%}
.ck-steps [data-state="current"] .ck-num{background:var(--ck-accent);color:#fff}
.ck-steps [data-state="done"]{color:var(--ck-ink)}
.ck-steps [data-state="done"]::before{width:100%}
.ck-steps [data-state="done"] .ck-num{background:var(--ck-soft);color:var(--ck-dark)}
.ck-steps [data-state="done"] .ck-num b{display:none}
.ck-steps [data-state="done"] .ck-num svg{display:block}
/* step panels and fields */
.ck-panel{margin-top:26px;display:grid;gap:16px;animation:ck-in .3s ease}
@keyframes ck-in{from{opacity:0;transform:translateY(6px)}}
.ck-h{margin:0;font-size:1.12rem;font-weight:700;letter-spacing:-.01em}
.ck-h:focus{outline:none}
.ck-kick{display:block;font-size:.72rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--ck-dark);margin-bottom:2px}
.ck-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px}
.ck-field{display:grid;gap:6px;min-width:0;align-content:start}
.ck-field > label,.ck-legend{font-size:.88rem;font-weight:600}
.ck-hint{font-size:.8rem;color:var(--ck-muted)}
.ck-input{width:100%;font:inherit;font-size:.95rem;color:var(--ck-ink);background:#fff;border:1.5px solid var(--ck-field);border-radius:10px;padding:10px 12px;min-height:44px}
.ck-input::placeholder{color:#6f6862}
.ck-input:focus-visible{outline:none;border-color:var(--ck-accent);box-shadow:0 0 0 4px var(--ck-soft)}
fieldset.ck-field{border:0;margin:0;padding:0}
.ck-legend{padding:0;margin-bottom:6px}
.ck-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}
.ck-tile{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border:1.5px solid var(--ck-field);border-radius:12px;cursor:pointer;transition:border-color .2s,background .2s}
.ck-tile input,.ck-check input{width:18px;height:18px;margin:2px 0 0;accent-color:var(--ck-accent);flex:none}
.ck-tile input:focus-visible,.ck-check input:focus-visible{outline:2px solid var(--ck-accent);outline-offset:3px}
.ck-tile > span{flex:1;display:grid;gap:2px}
.ck-tile b{font-size:.95rem}
.ck-tile span span{font-size:.82rem;color:var(--ck-muted)}
.ck-tile em{font-style:normal;font-weight:700;font-size:.92rem}
.ck-tile:has(:checked){border-color:var(--ck-accent);background:var(--ck-tint)}
.ck-check{display:flex;align-items:flex-start;gap:10px;font-size:.92rem;cursor:pointer}
/* review list */
.ck-review{margin:0;display:grid;border:1px solid var(--ck-rule);border-radius:12px;overflow:hidden}
.ck-review div{display:flex;justify-content:space-between;gap:16px;padding:11px 14px;font-size:.92rem}
.ck-review div + div{border-top:1px solid var(--ck-rule)}
.ck-review dt{color:var(--ck-muted);flex:none}
.ck-review dd{margin:0;font-weight:600;text-align:right;overflow-wrap:anywhere}
/* Back / Continue / Pay bar and done panel */
.ck-nav{display:flex;gap:10px;margin-top:26px;padding-top:20px;border-top:1px solid var(--ck-rule)}
.ck-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:46px;padding:0 22px;border-radius:10px;border:1.5px solid var(--ck-accent);background:var(--ck-accent);color:#fff;font:inherit;font-size:.95rem;font-weight:600;cursor:pointer;transition:background .2s,box-shadow .2s}
.ck-btn:hover{background:var(--ck-dark);border-color:var(--ck-dark)}
.ck-btn:focus-visible{outline:none;box-shadow:0 0 0 3px #fff,0 0 0 5px var(--ck-accent)}
.ck-btn.ghost{background:#fff;color:var(--ck-ink);border-color:var(--ck-field)}
.ck-btn.ghost:hover{background:var(--ck-tint)}
.ck-nav .ck-btn:not(.ghost){margin-left:auto}
.ck-done{text-align:center;justify-items:center;padding:12px 0 4px}
.ck-done > span{display:grid;place-items:center;width:56px;height:56px;border-radius:50%;background:var(--ck-soft);color:var(--ck-dark)}
.ck-done .ck-tick{width:26px;height:26px}
.ck-done p{margin:0;color:var(--ck-muted);font-size:.92rem;max-width:40ch}
/* order summary */
.ck-sum{padding:24px}
.ck-sum h3{margin:0 0 14px;font-size:1rem;font-weight:700}
.ck-items{list-style:none;margin:0;padding:0 0 16px;display:grid;gap:14px;border-bottom:1px solid var(--ck-rule)}
.ck-items li{display:flex;align-items:center;gap:12px;font-size:.9rem}
.ck-thumb{flex:none;display:grid;place-items:center;width:52px;height:52px;border-radius:12px;color:rgba(255,255,255,.9)}
.ck-thumb svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linejoin:round;stroke-linecap:round}
.ck-thumb.sand{background:linear-gradient(135deg,#e6d6b8,#c2a878)}
.ck-thumb.coal{background:linear-gradient(135deg,#6b6561,#35312f)}
.ck-item{display:grid;flex:1;min-width:0}
.ck-items small{font-size:.82rem;color:var(--ck-muted)}
.ck-items b{font-weight:600}
.ck-price{font-weight:600;font-variant-numeric:tabular-nums}
.ck-totals{margin:0;padding-top:14px;display:grid;gap:8px;font-size:.92rem}
.ck-totals div{display:flex;justify-content:space-between;gap:12px}
.ck-totals dt{color:var(--ck-muted)}
.ck-totals dd{margin:0;font-variant-numeric:tabular-nums}
.ck-totals .ck-grand{margin-top:6px;padding-top:12px;border-top:1px solid var(--ck-rule);font-size:1.05rem;font-weight:700}
.ck-totals .ck-grand dt{color:var(--ck-ink)}
@media (prefers-reduced-motion:reduce){.ck *,.ck *::before,.ck-prog *::before{transition:none!important;animation:none!important}}
@media (max-width:600px){.ck-main{padding:24px 18px}.ck-sum{padding:20px 18px}.ck-steps{gap:8px}.ck-steps li{gap:7px;font-size:.8rem}.ck-num{min-width:24px;height:24px}}
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.
// Checkout: the delivery choice ([data-cost]) updates [data-ship] and every [data-total]; [data-out-last4] shows only a card's last four digits.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li');
var back = wz.querySelector('[data-back]'), next = wz.querySelector('[data-next]');
var submit = wz.querySelector('[type="submit"]'), done = wz.querySelector('[data-done]'), at = 0;
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 price() {
if (!wz.dataset.subtotal) return;
var d = wz.querySelector('[data-cost]:checked'), c = d ? +d.dataset.cost : 0;
wz.querySelectorAll('[data-ship]').forEach(function (o) { o.textContent = c ? '$' + c.toFixed(2) : 'Free'; });
wz.querySelectorAll('[data-total]').forEach(function (o) { o.textContent = '$' + (+wz.dataset.subtotal + c).toFixed(2); });
}
function go(i, focus) {
at = i; mark(i); price();
panels.forEach(function (p, n) { p.hidden = n !== i; });
back.hidden = i === 0; next.hidden = i === panels.length - 1; submit.hidden = !next.hidden;
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';
});
wz.querySelectorAll('[data-out-last4]').forEach(function (o) {
var f = wz.elements[o.dataset.outLast4]; o.textContent = f ? f.value.replace(/\D/g, '').slice(-4) : '';
});
if (focus) panels[i].querySelector('[tabindex="-1"]').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('change', price);
back.addEventListener('click', function () { go(at - 1, true); });
next.addEventListener('click', function () { if (valid()) go(at + 1, true); });
wz.addEventListener('submit', function (e) {
e.preventDefault();
if (!valid()) return;
if (at < panels.length - 1) return go(at + 1, true);
// Last step: send the form here (fetch or wz.submit()). The demo shows the done panel instead.
panels[at].hidden = true; back.parentNode.hidden = true; mark(steps.length);
done.hidden = false; done.querySelector('[tabindex="-1"]').focus();
});
if (done) done.querySelector('[data-restart]').addEventListener('click', function () {
wz.reset(); done.hidden = true; back.parentNode.hidden = false; go(0, true);
});
go(0);
});
/* Checkout wizards on Bootstrap 5: .card, .breadcrumb, .row/.col, .form-control, .form-select, .form-check,
.list-group and .btn, restyled through Bootstrap's CSS variables. The chevrons and the stepper are custom.
A: a static progress trail for a checkout split over several pages (no script). B: a single-page checkout wizard.
Its script shows one step at a time, sets data-state and aria-current="step" on the stepper, moves focus to the
new step's heading, keeps shipping and totals in line with the delivery choice and shows only the card's last
four digits in the review. Without JavaScript every step shows and the Pay button submits the form normally. */
.ck,.ck-prog{--ck-accent:#c2410c;--ck-dark:#9a3412;--ck-soft:#ffedd5;--ck-tint:#fff7ed;--ck-ink:#1c1917;--ck-muted:#57534e;
--ck-field:#8f867e;--ck-track:#e2dcd4;--ck-rule:#e7e2da;--ck-stone:#efebe5;
--bs-border-color:#8f867e;--bs-border-radius:10px;--bs-body-color:#1c1917;--bs-secondary-color:#57534e;color:var(--ck-ink)}
.ck-card{--bs-card-border-color:var(--ck-rule);--bs-card-border-radius:16px;--bs-card-inner-border-radius:15px;box-shadow:0 1px 2px rgba(28,25,23,.05),0 12px 28px -18px rgba(124,45,18,.22)}
.ck-secure{display:inline-flex;align-items:center;gap:6px;font-size:.82rem;color:var(--ck-muted)}
.ck-secure svg,.ck .btn svg{width:15px;height:15px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
.ck-tick{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;flex:none}
/* A1 chevrons: the arrow shape is a clip-path on ::before, so focus outlines on the links are never clipped */
.ck-prog{display:grid;gap:16px}
.ck-top{padding:22px 26px 26px}
.ck-brand{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:18px}
.ck-brand b{display:inline-flex;align-items:center;gap:9px;font-size:1.05rem;letter-spacing:-.01em}
.ck-brand b::before{content:"";width:22px;height:22px;border-radius:7px;background:var(--ck-accent);box-shadow:inset -6px -6px 0 var(--ck-dark)}
.ck-chevs ol{list-style:none;margin:0;padding:0;display:flex}
.ck-chevs li{position:relative;isolation:isolate;flex:1;min-width:0;display:flex;height:50px;padding:0 14px;font-size:.92rem;color:var(--ck-muted)}
.ck-chevs li + li{margin-left:-10px}
.ck-chevs li::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--ck-stone);transition:background .2s;
clip-path:polygon(0 0,calc(100% - 14px) 0,100% 50%,calc(100% - 14px) 100%,0 100%,14px 50%)}
.ck-chevs li:first-child{padding-left:0}
.ck-chevs li:first-child::before{border-radius:10px 0 0 10px;clip-path:polygon(0 0,calc(100% - 14px) 0,100% 50%,calc(100% - 14px) 100%,0 100%)}
.ck-chevs li:last-child{padding-right:0}
.ck-chevs li:last-child::before{border-radius:0 10px 10px 0;clip-path:polygon(0 0,100% 0,100% 100%,0 100%,14px 50%)}
.ck-chevs a,.ck-chevs .ck-in{flex:1;min-width:0;display:flex;align-items:center;justify-content:center;gap:8px;color:inherit;text-decoration:none;border-radius:6px;white-space:nowrap}
.ck-chevs a:hover .ck-name{text-decoration:underline;text-underline-offset:3px}
.ck-chevs a:focus-visible{outline:2px solid var(--ck-ink);outline-offset:-4px}
.ck-chevs [data-state="done"]{color:var(--ck-dark);font-weight:600}
.ck-chevs [data-state="done"]::before{background:var(--ck-soft)}
.ck-chevs [data-state="done"]:hover::before{background:#fed7aa}
.ck-chevs [data-state="current"]{color:#fff;font-weight:600}
.ck-chevs [data-state="current"]::before{background:var(--ck-accent)}
.ck-n{display:grid;place-items:center;width:24px;height:24px;border-radius:50%;border:1.5px solid currentColor;font-size:.76rem;font-weight:700;flex:none}
/* A2 slim line: Bootstrap's .breadcrumb with its divider switched off, small numbered circles, done steps are links */
.ck-slim{padding:14px 22px}
.ck-line{--bs-breadcrumb-divider:none;--bs-breadcrumb-margin-bottom:0;--bs-breadcrumb-item-padding-x:0;--bs-breadcrumb-font-size:.9rem;--bs-breadcrumb-item-active-color:var(--ck-ink);align-items:center;gap:8px 12px;color:var(--ck-muted)}
.ck-line .breadcrumb-item{display:flex;align-items:center;gap:8px}
.ck-sep{color:#78716c;margin-left:4px;font-weight:400}
.ck-line a{display:inline-flex;align-items:center;gap:8px;color:var(--ck-dark);font-weight:600;text-decoration:underline;text-decoration-color:rgba(154,52,18,.35);text-underline-offset:4px;border-radius:4px}
.ck-line a:hover{text-decoration-color:currentColor}
.ck-line a:focus-visible{outline:2px solid var(--ck-accent);outline-offset:3px}
.ck-c{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;border:1.5px solid currentColor;font-size:.72rem;font-weight:700;flex:none}
.ck-c .ck-tick{width:13px;height:13px}
.ck-line [data-state="done"] .ck-c{background:var(--ck-soft);border-color:var(--ck-soft)}
.ck-line [data-state="current"]{font-weight:600}
.ck-line [data-state="current"] .ck-c{background:var(--ck-accent);border-color:var(--ck-accent);color:#fff}
@media (max-width:560px){.ck-top{padding:18px 16px 20px}
.ck-chevs li{flex:none;width:60px;height:46px}.ck-chevs li:first-child{width:50px}.ck-chevs [data-state="current"]{flex:1;width:auto}
.ck-chevs li:not([data-state="current"]) .ck-name{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
.ck-slim{padding:12px 16px}.ck-line{--bs-breadcrumb-font-size:.84rem;gap:6px 8px}.ck-line .breadcrumb-item{gap:6px}.ck-sep{margin-left:2px}.ck-c{width:20px;height:20px}}
/* B: form card and order summary in a .row, stacked below lg (992px) */
.ck-main{padding:28px 32px}
.ck-head{display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap}
.ck-title{font-size:1.35rem;letter-spacing:-.015em;line-height:1.5}
@media (min-width:992px){.ck-sum{position:sticky;top:20px}}
/* stepper: zero-padded numbers over a thin track that fills to half on the current step and fully when done */
.ck-steps{list-style:none;margin:22px 0 0;padding:0;display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.ck-steps li{position:relative;display:flex;align-items:center;gap:10px;padding-bottom:12px;font-size:.9rem;color:var(--ck-muted);min-width:0}
.ck-steps li::after,.ck-steps li::before{content:"";position:absolute;left:0;bottom:0;height:3px;border-radius:3px}
.ck-steps li::after{right:0;background:var(--ck-track)}
.ck-steps li::before{width:0;z-index:1;background:var(--ck-accent);transition:width .35s ease}
.ck-num{display:grid;place-items:center;min-width:28px;height:26px;padding:0 4px;border-radius:7px;background:var(--ck-stone);font-size:.78rem;font-weight:700;font-variant-numeric:tabular-nums;letter-spacing:.03em;transition:background .25s,color .25s}
.ck-num svg{display:none;width:15px;height:15px}
.ck-steps [data-state="current"]{color:var(--ck-ink);font-weight:600}
.ck-steps [data-state="current"]::before{width:50%}
.ck-steps [data-state="current"] .ck-num{background:var(--ck-accent);color:#fff}
.ck-steps [data-state="done"]{color:var(--ck-ink)}
.ck-steps [data-state="done"]::before{width:100%}
.ck-steps [data-state="done"] .ck-num{background:var(--ck-soft);color:var(--ck-dark)}
.ck-steps [data-state="done"] .ck-num b{display:none}
.ck-steps [data-state="done"] .ck-num svg{display:block}
/* step panels and Bootstrap form controls */
.ck-panel{margin-top:26px;display:grid;gap:16px;animation:ck-in .3s ease}
@keyframes ck-in{from{opacity:0;transform:translateY(6px)}}
.ck-h{font-size:1.12rem;letter-spacing:-.01em;line-height:1.5}
.ck-h:focus{outline:none}
.ck-kick{display:block;font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;color:var(--ck-dark);margin-bottom:2px}
.ck .form-label,.ck legend{font-size:.88rem;font-weight:600;margin-bottom:6px}
.ck .form-text{font-size:.8rem;color:var(--ck-muted);margin-top:6px}
.ck .form-control,.ck .form-select{border-width:1.5px;min-height:44px;padding:10px 12px;font-size:.95rem}
.ck .form-select{padding-right:36px}
.ck .form-control::placeholder{color:#6f6862}
.ck .form-control:focus,.ck .form-select:focus{border-color:var(--ck-accent);box-shadow:0 0 0 4px var(--ck-soft)}
.ck .form-check-input{width:18px;height:18px;border-width:1.5px;border-color:var(--ck-field);margin-top:2px}
.ck .form-check-input:checked{background-color:var(--ck-accent);border-color:var(--ck-accent)}
.ck .form-check-input:focus{border-color:var(--ck-accent);box-shadow:none}
.ck .form-check-input:focus-visible{outline:2px solid var(--ck-accent);outline-offset:3px}
.ck .form-check{display:flex;align-items:flex-start;gap:10px;padding-left:0;margin:0;font-size:.92rem}
.ck .form-check .form-check-input{float:none;margin-left:0;flex:none}
.ck-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}
.ck-tile{display:flex;align-items:flex-start;gap:12px;padding:14px 16px;border:1.5px solid var(--ck-field);border-radius:12px;cursor:pointer;transition:border-color .2s,background .2s}
.ck-tile .form-check-input{margin:2px 0 0;flex:none}
.ck-tile > span{flex:1;display:grid;gap:2px}
.ck-tile b{font-size:.95rem}
.ck-tile span span{font-size:.82rem;color:var(--ck-muted)}
.ck-tile em{font-style:normal;font-weight:700;font-size:.92rem}
.ck-tile:has(:checked){border-color:var(--ck-accent);background:var(--ck-tint)}
.ck .list-group{--bs-list-group-border-color:var(--ck-rule);--bs-list-group-border-radius:12px;--bs-list-group-item-padding-y:11px;--bs-list-group-item-padding-x:14px;font-size:.92rem}
.ck .list-group dt{font-weight:400;color:var(--ck-muted);flex:none}
.ck .list-group dd{margin:0;font-weight:600;text-align:right;overflow-wrap:anywhere}
/* Back / Continue / Pay bar and done panel */
.ck .btn{--bs-btn-padding-x:22px;--bs-btn-font-weight:600;--bs-btn-font-size:.95rem;--bs-btn-border-width:1.5px;--bs-btn-border-radius:10px;min-height:46px;display:inline-flex;align-items:center;justify-content:center;gap:8px}
.ck .btn-primary{--bs-btn-bg:var(--ck-accent);--bs-btn-border-color:var(--ck-accent);--bs-btn-hover-bg:var(--ck-dark);--bs-btn-hover-border-color:var(--ck-dark);--bs-btn-active-bg:var(--ck-dark);--bs-btn-active-border-color:var(--ck-dark)}
.ck .btn-outline-secondary{--bs-btn-color:var(--ck-ink);--bs-btn-bg:#fff;--bs-btn-border-color:var(--ck-field);--bs-btn-hover-bg:var(--ck-tint);--bs-btn-hover-color:var(--ck-ink);--bs-btn-hover-border-color:var(--ck-field);--bs-btn-active-bg:var(--ck-soft);--bs-btn-active-color:var(--ck-ink);--bs-btn-active-border-color:var(--ck-field)}
.ck .btn:focus-visible{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--ck-accent);outline:0}
.ck-nav{display:flex;gap:10px;margin-top:26px;padding-top:20px;border-top:1px solid var(--ck-rule)}
.ck-nav .btn-primary{margin-left:auto}
.ck-done{text-align:center;justify-items:center;padding:12px 0 4px}
.ck-done > span{display:grid;place-items:center;width:56px;height:56px;border-radius:50%;background:var(--ck-soft);color:var(--ck-dark)}
.ck-done .ck-tick{width:26px;height:26px}
.ck-done p{color:var(--ck-muted);font-size:.92rem;max-width:40ch}
/* order summary */
.ck-sum{padding:24px}
.ck-items{display:grid;gap:14px;padding-bottom:16px;border-bottom:1px solid var(--ck-rule)}
.ck-items li{display:flex;align-items:center;gap:12px;font-size:.9rem}
.ck-thumb{flex:none;display:grid;place-items:center;width:52px;height:52px;border-radius:12px;color:rgba(255,255,255,.9)}
.ck-thumb svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linejoin:round;stroke-linecap:round}
.ck-thumb.sand{background:linear-gradient(135deg,#e6d6b8,#c2a878)}
.ck-thumb.coal{background:linear-gradient(135deg,#6b6561,#35312f)}
.ck-item{display:grid;flex:1;min-width:0}
.ck-items small{font-size:.82rem;color:var(--ck-muted)}
.ck-items b{font-weight:600}
.ck-price{font-weight:600;font-variant-numeric:tabular-nums}
.ck-totals{padding-top:14px;display:grid;gap:8px;font-size:.92rem}
.ck-totals dt{font-weight:400;color:var(--ck-muted)}
.ck-totals dd{margin:0;font-variant-numeric:tabular-nums}
.ck-totals .ck-grand{margin-top:6px;padding-top:12px;border-top:1px solid var(--ck-rule);font-size:1.05rem;font-weight:700}
.ck-totals .ck-grand dt{font-weight:700;color:var(--ck-ink)}
@media (prefers-reduced-motion:reduce){.ck *,.ck *::before,.ck-prog *::before{transition:none!important;animation:none!important}}
@media (max-width:600px){.ck-main{padding:24px 18px}.ck-sum{padding:20px 18px}.ck-steps{gap:8px}.ck-steps li{gap:7px;font-size:.8rem}.ck-num{min-width:24px;height:24px}}
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.
// Checkout: the delivery choice ([data-cost]) updates [data-ship] and every [data-total]; [data-out-last4] shows only a card's last four digits.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li');
var back = wz.querySelector('[data-back]'), next = wz.querySelector('[data-next]');
var submit = wz.querySelector('[type="submit"]'), done = wz.querySelector('[data-done]'), at = 0;
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 price() {
if (!wz.dataset.subtotal) return;
var d = wz.querySelector('[data-cost]:checked'), c = d ? +d.dataset.cost : 0;
wz.querySelectorAll('[data-ship]').forEach(function (o) { o.textContent = c ? '$' + c.toFixed(2) : 'Free'; });
wz.querySelectorAll('[data-total]').forEach(function (o) { o.textContent = '$' + (+wz.dataset.subtotal + c).toFixed(2); });
}
function go(i, focus) {
at = i; mark(i); price();
panels.forEach(function (p, n) { p.hidden = n !== i; });
back.hidden = i === 0; next.hidden = i === panels.length - 1; submit.hidden = !next.hidden;
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';
});
wz.querySelectorAll('[data-out-last4]').forEach(function (o) {
var f = wz.elements[o.dataset.outLast4]; o.textContent = f ? f.value.replace(/\D/g, '').slice(-4) : '';
});
if (focus) panels[i].querySelector('[tabindex="-1"]').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('change', price);
back.addEventListener('click', function () { go(at - 1, true); });
next.addEventListener('click', function () { if (valid()) go(at + 1, true); });
wz.addEventListener('submit', function (e) {
e.preventDefault();
if (!valid()) return;
if (at < panels.length - 1) return go(at + 1, true);
// Last step: send the form here (fetch or wz.submit()). The demo shows the done panel instead.
panels[at].hidden = true; back.parentNode.hidden = true; mark(steps.length);
done.hidden = false; done.querySelector('[tabindex="-1"]').focus();
});
if (done) done.querySelector('[data-restart]').addEventListener('click', function () {
wz.reset(); done.hidden = true; back.parentNode.hidden = false; go(0, true);
});
go(0);
});
// 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.
// Checkout: the delivery choice ([data-cost]) updates [data-ship] and every [data-total]; [data-out-last4] shows only a card's last four digits.
document.querySelectorAll('[data-wizard]').forEach(function (wz) {
var panels = wz.querySelectorAll('[data-step]'), steps = wz.querySelectorAll('[data-stepper] > li');
var back = wz.querySelector('[data-back]'), next = wz.querySelector('[data-next]');
var submit = wz.querySelector('[type="submit"]'), done = wz.querySelector('[data-done]'), at = 0;
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 price() {
if (!wz.dataset.subtotal) return;
var d = wz.querySelector('[data-cost]:checked'), c = d ? +d.dataset.cost : 0;
wz.querySelectorAll('[data-ship]').forEach(function (o) { o.textContent = c ? '$' + c.toFixed(2) : 'Free'; });
wz.querySelectorAll('[data-total]').forEach(function (o) { o.textContent = '$' + (+wz.dataset.subtotal + c).toFixed(2); });
}
function go(i, focus) {
at = i; mark(i); price();
panels.forEach(function (p, n) { p.hidden = n !== i; });
back.hidden = i === 0; next.hidden = i === panels.length - 1; submit.hidden = !next.hidden;
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';
});
wz.querySelectorAll('[data-out-last4]').forEach(function (o) {
var f = wz.elements[o.dataset.outLast4]; o.textContent = f ? f.value.replace(/\D/g, '').slice(-4) : '';
});
if (focus) panels[i].querySelector('[tabindex="-1"]').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('change', price);
back.addEventListener('click', function () { go(at - 1, true); });
next.addEventListener('click', function () { if (valid()) go(at + 1, true); });
wz.addEventListener('submit', function (e) {
e.preventDefault();
if (!valid()) return;
if (at < panels.length - 1) return go(at + 1, true);
// Last step: send the form here (fetch or wz.submit()). The demo shows the done panel instead.
panels[at].hidden = true; back.parentNode.hidden = true; mark(steps.length);
done.hidden = false; done.querySelector('[tabindex="-1"]').focus();
});
if (done) done.querySelector('[data-restart]').addEventListener('click', function () {
wz.reset(); done.hidden = true; back.parentNode.hidden = false; go(0, true);
});
go(0);
});
About this set
Two checkout patterns in a warm stone and burnt-orange palette. Progress for a multi-page checkout is a static trail for stores that put Cart, Shipping, Payment and Review on separate pages. It comes in two styles: chevron segments cut with clip-path, and a slim line with small numbered circles. Completed steps are links with a check icon, the current step carries aria-current=”step” and upcoming steps are plain text, with no JavaScript at all. On a phone the chevrons shrink to icons and numbers and keep only the current label. The single-page checkout is a three-step form wizard, Shipping, Payment and Review, beside an order summary that stacks under the form on narrow screens. Choosing Standard or Express delivery updates the shipping line, the total and the Pay button text. The payment step uses the right autocomplete tokens, numeric keyboards and patterns for card number, expiry and security code, and the review shows only the card’s last four digits. A small vanilla script shows one step at a time, checks each step with the browser’s own validation before moving on and moves focus to the new step’s heading. Without JavaScript every step shows and the Pay button submits the form normally.
What’s included
Chevron and slim numbered progress trails for multi-page checkouts, no JavaScript
Single-page Shipping, Payment and Review wizard with an order summary
Delivery choice updates shipping, total and the Pay button text
Card fields with cc-* autocomplete, numeric keyboards and format patterns
Review shows only the card's last four digits
Works without JavaScript: all steps show and the form submits normally
Accessibility
Both progress trails are nav landmarks with an ordered list: completed steps are links that announce as completed, the current step has aria-current="step" and upcoming steps are plain text at 4.5:1. The wizard stepper updates aria-current and visually hidden completed text, focus moves to each new step's heading, which starts with Step 2 of 3, and the order total is announced politely when the delivery choice changes. Every link, field and button has a visible focus style, field borders reach 3:1, and transitions and the step animation are switched off under prefers-reduced-motion.
Customise it
Change the --ck-accent, --ck-dark, --ck-soft and --ck-tint custom properties in the plain and Bootstrap versions (Bootstrap buttons and fields read them through --bs-btn-* and --bs-border-color), or the --color-ck-* values in the Tailwind @theme block. Set data-subtotal on the form and data-cost on each delivery option to match your prices.