Open demo

CSS code

HTML
<div class="demo-row">
<div><span class="lbl">Summary with promo code</span>
<section class="sm" data-summary data-subtotal="387.00" data-ship="25.00" data-tax="8" data-codes="{"SPRING15": {"percent": 15}, "FREESHIP": {"freeShipping": true}}" aria-labelledby="sm1-title">
  <h2 class="sm-title" id="sm1-title">Order summary</h2>
  <ul class="sm-items">
    <li class="sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1503602642458-232111445657?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="sm-qty" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Ash stool</p><p class="sm-meta">Natural oil finish<span class="sr-only">, quantity 1</span></p></div><p class="sm-price">$89.00</p></li>
    <li class="sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1592078615290-033ee584e267?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="sm-qty" aria-hidden="true">2</span></div>
      <div><p class="sm-name">Shell dining chair</p><p class="sm-meta">Black · Oak legs<span class="sr-only">, quantity 2</span></p></div><p class="sm-price">$298.00</p></li>
  </ul>
  <form class="sm-promo" action="#" data-promo>
    <label class="sm-label" for="sm1-code">Promo code</label>
    <div class="sm-field"><input class="sm-input" id="sm1-code" name="promo" type="text" autocomplete="off" spellcheck="false" placeholder="e.g. SPRING15" aria-describedby="sm1-hint sm1-err"><button class="sm-apply" type="submit">Apply</button></div>
    <p class="sm-hint" id="sm1-hint">Try SPRING15 or FREESHIP in this demo.</p>
    <p class="sm-err" id="sm1-err" data-promo-error></p>
    <p class="sm-chip" data-applied hidden><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12V4h8l10 10-8 8z"/><circle cx="7.5" cy="8.5" r="1.3"/></svg><span data-applied-code></span><button type="button" data-remove-code aria-label="Remove promo code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></p>
  </form>
    <dl class="sm-rows">
      <div class="sm-row"><dt>Subtotal</dt><dd data-subtotal>$387.00</dd></div>
      <div class="sm-row sm-row--save" data-discount-row hidden><dt>Discount (<span data-applied-code></span>)</dt><dd data-discount>−$0.00</dd></div>
      <div class="sm-row"><dt>Shipping</dt><dd data-shipping>$25.00</dd></div>
      <div class="sm-row"><dt>Estimated tax (8%)</dt><dd data-tax>$30.96</dd></div>
    </dl>
    <dl class="sm-total"><dt>Total</dt><dd><small>USD</small><span data-total>$442.96</span></dd></dl>
  <a class="sm-btn" href="#"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>Continue to payment</a>
  <p class="sr-only" role="status" data-announce></p>
</section>
</div>
<div><span class="lbl">Free-shipping progress</span>
<section class="sm" data-summary data-subtotal="53.00" data-ship="6.50" data-free-over="75" aria-labelledby="sm2-title">
  <h2 class="sm-title" id="sm2-title">Your order</h2>
  <div class="sm-free"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 6h12v10H2zM14 9h4l3 3v4h-7"/><circle cx="6" cy="18" r="2"/><circle cx="17" cy="18" r="2"/></svg><p id="sm2-free" data-free-msg>You are $22.00 away from free shipping.</p>
    <div class="sm-track" role="progressbar" aria-labelledby="sm2-free" aria-valuemin="0" aria-valuemax="100" aria-valuenow="71"><span class="sm-fill" data-free-bar style="width:71%"></span></div></div>
  <ul class="sm-items">
    <li class="sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1602874801007-bd458bb1b8b6?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="sm-qty" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Cedar and fig candle</p><p class="sm-meta">220 g · 45-hour burn<span class="sr-only">, quantity 1</span></p></div><p class="sm-price">$32.00</p></li>
    <li class="sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="sm-qty" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Amber room oil</p><p class="sm-meta">30 ml dropper<span class="sr-only">, quantity 1</span></p></div><p class="sm-price">$21.00</p></li>
  </ul>
  <label class="sm-addon"><input type="checkbox" name="addon" value="travel-tin" data-extra data-name="Travel tin candle" data-price="24.00"><span class="sm-addon-art"><svg width="30" height="30" viewBox="0 0 32 32" aria-hidden="true"><ellipse cx="16" cy="12" rx="11" ry="4" fill="#d6cfc4"/><path d="M5 12v8c0 2.2 4.9 4 11 4s11-1.8 11-4v-8" fill="#b9ae9f"/><ellipse cx="16" cy="12" rx="8" ry="2.6" fill="#efe8dc"/><path d="M16 6v5" stroke="#3b3a36" stroke-width="1.4" stroke-linecap="round"/><path d="M16 3.5c1.4 1.4 1.4 2.6 0 3.6-1.4-1-1.4-2.2 0-3.6z" fill="#e8913a"/></svg></span>
    <span><strong>Add a travel tin candle</strong><span>Same cedar and fig scent, 25-hour burn</span></span><b>+$24.00</b></label>
    <dl class="sm-rows">
      <div class="sm-row"><dt>Subtotal</dt><dd data-subtotal>$53.00</dd></div>
      <div class="sm-row sm-row--save" data-discount-row hidden><dt>Discount (<span data-applied-code></span>)</dt><dd data-discount>−$0.00</dd></div>
      <div class="sm-row"><dt>Shipping</dt><dd data-shipping>$6.50</dd></div>
      
    </dl>
    <dl class="sm-total"><dt>Total</dt><dd><small>USD</small><span data-total>$59.50</span></dd></dl>
  <p class="sm-incl">Taxes included.</p>
  <a class="sm-btn" href="#"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>Checkout</a>
  <p class="sr-only" role="status" data-announce></p>
</section>
</div>
</div>
<div class="demo-narrow"><span class="lbl">Collapsible summary for phones</span>
<details class="sm sm-details" open>
  <summary>Order summary <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg><b>$442.96</b></summary>
  <div class="sm-details-body">
  <ul class="sm-items">
    <li class="sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1503602642458-232111445657?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="sm-qty" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Ash stool</p><p class="sm-meta">Natural oil finish<span class="sr-only">, quantity 1</span></p></div><p class="sm-price">$89.00</p></li>
    <li class="sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1592078615290-033ee584e267?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="sm-qty" aria-hidden="true">2</span></div>
      <div><p class="sm-name">Shell dining chair</p><p class="sm-meta">Black · Oak legs<span class="sr-only">, quantity 2</span></p></div><p class="sm-price">$298.00</p></li>
  </ul>
    <dl class="sm-rows">
      <div class="sm-row"><dt>Subtotal</dt><dd>$387.00</dd></div>
      <div class="sm-row"><dt>Shipping</dt><dd>$25.00</dd></div>
      <div class="sm-row"><dt>Estimated tax (8%)</dt><dd>$30.96</dd></div>
    </dl>
    <dl class="sm-total"><dt>Total</dt><dd><small>USD</small><span>$442.96</span></dd></dl>
  </div>
</details>
</div>
CSS
/* Order summary panels. Totals are recalculated in cents by the script from data-subtotal, data-ship,
   data-tax and data-codes on each [data-summary]; the collapsible version is a native <details>. */
.sm{--sm-accent:#0f766e;--sm-accent-dark:#115e59;--sm-ink:#10201e;--sm-muted:#4b5f5c;--sm-line:#dfe7e5;--sm-field:#6b7f7c;--sm-soft:#f0f5f4;
  --sm-ring:rgba(15,118,110,.3);--sm-error:#b42318;--sm-save:#0f766e;color:var(--sm-ink);font-size:.93rem;line-height:1.45;
  background:#fff;border-radius:18px;padding:24px;box-shadow:0 1px 2px rgba(16,32,30,.06),0 18px 40px -24px rgba(16,32,30,.35)}
.sm [hidden]{display:none!important}
.sm .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}
.sm :where(button,input){font:inherit}
.sm :is(a,button,summary):focus-visible{outline:2px solid var(--sm-accent);outline-offset:2px}
.sm-title{font-size:1.1rem;font-weight:700;margin-bottom:6px}
.sm-items{list-style:none;border-bottom:1px solid var(--sm-line);padding-bottom:6px}
.sm-item{display:grid;grid-template-columns:60px minmax(0,1fr) auto;gap:14px;align-items:center;padding:10px 0}
.sm-thumb{position:relative;width:60px;height:60px}
.sm-thumb img{width:60px;height:60px;border-radius:12px;object-fit:cover;border:1px solid var(--sm-line);background:var(--sm-soft)}
.sm-qty{position:absolute;top:-7px;right:-7px;min-width:22px;height:22px;padding:0 6px;border-radius:999px;background:var(--sm-muted);color:#fff;font-size:.72rem;font-weight:700;display:grid;place-items:center}
.sm-name{font-weight:600;line-height:1.3}
.sm-meta{color:var(--sm-muted);font-size:.82rem}
.sm-price{font-weight:600;font-variant-numeric:tabular-nums}
/* promo code */
.sm-promo{padding:16px 0;border-bottom:1px solid var(--sm-line)}
.sm-label{display:block;font-size:.84rem;font-weight:600;margin-bottom:6px}
.sm-field{display:flex;gap:8px}
.sm-input{flex:1;min-width:0;padding:10px 12px;border:1px solid var(--sm-field);border-radius:10px;color:inherit;background:#fff;text-transform:uppercase;letter-spacing:.04em}
.sm-input::placeholder{text-transform:none;letter-spacing:0;color:#64748b}
.sm-input:focus{outline:none;border-color:var(--sm-accent);box-shadow:0 0 0 3px var(--sm-ring)}
.sm-input[aria-invalid="true"]{border-color:var(--sm-error)}
.sm-input[aria-invalid="true"]:focus{box-shadow:0 0 0 3px rgba(180,35,24,.2)}
.sm-apply{padding:10px 16px;border:1px solid var(--sm-ink);border-radius:10px;background:var(--sm-ink);color:#fff;font-weight:700;cursor:pointer;transition:background-color .15s}
.sm-apply:hover{background:#25403c}
.sm-hint{font-size:.78rem;color:var(--sm-muted);margin-top:6px}
.sm-err{display:flex;gap:6px;color:var(--sm-error);font-size:.8rem;font-weight:600;margin-top:6px}
.sm-err::before{content:"!";flex:none;width:16px;height:16px;margin-top:1px;border-radius:50%;background:var(--sm-error);color:#fff;font-size:.7rem;font-weight:800;display:grid;place-items:center}
.sm-err:empty{display:none}
.sm-chip{display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:4px 4px 4px 10px;border-radius:999px;background:#e3f1ef;color:var(--sm-accent-dark);font-size:.8rem;font-weight:700;letter-spacing:.03em}
.sm-chip button{width:24px;height:24px;display:grid;place-items:center;border:0;border-radius:50%;background:none;color:inherit;cursor:pointer}
.sm-chip button:hover{background:#c9e6e2}
/* totals */
.sm-rows{display:grid;gap:8px;padding:16px 0;border-bottom:1px solid var(--sm-line)}
.sm-row{display:flex;justify-content:space-between;gap:12px}
.sm-row dt{color:var(--sm-muted)}
.sm-row dd{font-weight:600;font-variant-numeric:tabular-nums;text-align:right}
.sm-row--save dt,.sm-row--save dd{color:var(--sm-save);font-weight:700}
.sm-total{display:flex;justify-content:space-between;align-items:baseline;padding-top:16px}
.sm-total dt{font-weight:700;font-size:1.05rem}
.sm-total dd{font-weight:800;font-size:1.4rem;font-variant-numeric:tabular-nums}
.sm-total small{font-size:.75rem;font-weight:600;color:var(--sm-muted);margin-right:6px}
.sm-btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;margin-top:18px;padding:13px 18px;border:0;border-radius:12px;background:var(--sm-accent);color:#fff;
  font-weight:700;text-decoration:none;cursor:pointer;transition:background-color .15s}
.sm-btn:hover{background:var(--sm-accent-dark)}
/* free-shipping progress */
.sm-free{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;align-items:center;padding:14px 16px;border-radius:14px;background:var(--sm-soft);margin:12px 0 4px}
.sm-free svg{grid-row:span 2;color:var(--sm-accent)}
.sm-free p{font-weight:600;font-size:.88rem}
.sm-track{height:8px;border-radius:99px;background:#cfdcd9;overflow:hidden}
.sm-fill{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,#14b8a6,var(--sm-accent));transition:width .4s}
.sm-addon{display:grid;grid-template-columns:auto 40px minmax(0,1fr) auto;gap:12px;align-items:center;margin-top:12px;padding:12px 14px;border:1px dashed var(--sm-field);border-radius:14px;cursor:pointer}
.sm-addon:has(:checked){border-style:solid;border-color:var(--sm-accent);background:#f3faf9}
.sm-addon input{appearance:none;width:20px;height:20px;margin:0;border:1.5px solid var(--sm-field);border-radius:6px;background:#fff center/13px no-repeat;cursor:pointer}
.sm-addon input:checked{background-color:var(--sm-accent);border-color:var(--sm-accent);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23fff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")}
.sm-addon input:focus-visible{outline:2px solid var(--sm-accent);outline-offset:2px}
.sm-addon-art{width:40px;height:40px;border-radius:10px;background:#f6f1ea;display:grid;place-items:center}
.sm-addon strong{display:block;font-size:.88rem}
.sm-addon span{font-size:.8rem;color:var(--sm-muted)}
.sm-addon b{font-variant-numeric:tabular-nums}
.sm-incl{font-size:.78rem;color:var(--sm-muted);margin-top:4px;text-align:right}
/* collapsible */
.sm-details{padding:0;overflow:hidden}
.sm-details summary{display:flex;align-items:center;gap:8px;padding:16px 20px;cursor:pointer;list-style:none;background:var(--sm-soft);color:var(--sm-accent-dark);font-weight:700}
.sm-details summary::-webkit-details-marker{display:none}
.sm-details summary svg{transition:transform .2s}
.sm-details[open] summary svg{transform:rotate(180deg)}
.sm-details summary b{margin-left:auto;color:var(--sm-ink);font-size:1.1rem;font-variant-numeric:tabular-nums}
.sm-details summary:focus-visible{outline-offset:-3px}
.sm-details-body{padding:8px 20px 20px}
@media (max-width:420px){.sm{padding:20px 18px}.sm-details{padding:0}}
@media (prefers-reduced-motion:reduce){.sm-fill,.sm-btn,.sm-apply,.sm-details summary svg{transition:none}}
JavaScript
// Order summary: recalculates discount, shipping, tax and total in cents when a promo code is applied or
// removed, or an add-on is ticked. Codes live in data-codes on each [data-summary]; they are checked in the
// browser for this demo only, so check them again on the server before charging anything.
document.querySelectorAll('[data-summary]').forEach(function (box) {
  var money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
  var cents = function (v) { return Math.round(parseFloat(v) * 100); };
  var fmt = function (c) { return money.format(c / 100); };
  var codes = JSON.parse(box.dataset.codes || '{}'), rate = parseFloat(box.dataset.tax || 0);
  var freeOver = box.dataset.freeOver ? cents(box.dataset.freeOver) : Infinity, fee = cents(box.dataset.ship || 0);
  var status = box.querySelector('[data-announce]'), applied = '';
  function put(sel, text) { box.querySelectorAll(sel).forEach(function (el) { el.textContent = text; }); }
  function say(text) { if (status) status.textContent = text; }
  function render() {
    var sub = cents(box.dataset.subtotal);
    box.querySelectorAll('[data-extra]:checked').forEach(function (x) { sub += cents(x.dataset.price); });
    var code = codes[applied] || {};
    var off = code.percent ? Math.round(sub * code.percent / 100) : 0;
    var ship = code.freeShipping || sub >= freeOver ? 0 : fee;
    var tax = Math.round((sub - off) * rate / 100), total = sub - off + ship + tax;
    put('[data-subtotal]', fmt(sub)); put('[data-discount]', '−' + fmt(off)); put('[data-tax]', fmt(tax));
    put('[data-shipping]', ship ? fmt(ship) : 'Free'); put('[data-total]', fmt(total)); put('[data-applied-code]', applied);
    box.querySelectorAll('[data-discount-row]').forEach(function (el) { el.hidden = !off; });
    box.querySelectorAll('[data-applied]').forEach(function (el) { el.hidden = !applied; });
    put('[data-free-msg]', sub >= freeOver ? 'You have unlocked free shipping.' : 'You are ' + fmt(freeOver - sub) + ' away from free shipping.');
    box.querySelectorAll('[data-free-bar]').forEach(function (bar) {
      var pct = Math.min(100, Math.round(sub / freeOver * 100));
      bar.style.width = pct + '%'; bar.parentElement.setAttribute('aria-valuenow', pct);
    });
    return { off: off, ship: ship, total: total };
  }
  var form = box.querySelector('[data-promo]');
  if (form) {
    var input = form.querySelector('input'), err = form.querySelector('[data-promo-error]');
    form.noValidate = true;
    form.addEventListener('submit', function (e) {
      e.preventDefault(); // demo only: nothing is sent
      var code = input.value.trim().toUpperCase();
      var msg = !code ? 'Enter a promo code.' : codes[code] ? '' : code + ' is not a valid code. Check the spelling and try again.';
      err.textContent = msg;
      if (msg) { input.setAttribute('aria-invalid', 'true'); input.focus(); return; }
      input.removeAttribute('aria-invalid'); input.value = ''; applied = code;
      var t = render();
      say('Promo code ' + code + ' applied. ' + (t.off ? 'You save ' + fmt(t.off) + '. ' : 'Shipping is free. ') + 'New total ' + fmt(t.total) + '.');
    });
    input.addEventListener('input', function () { if (err.textContent) { err.textContent = ''; input.removeAttribute('aria-invalid'); } });
  }
  box.addEventListener('click', function (e) {
    if (!e.target.closest('[data-remove-code]')) return;
    applied = ''; var t = render();
    say('Promo code removed. New total ' + fmt(t.total) + '.');
    if (form) form.querySelector('input').focus();
  });
  box.addEventListener('change', function (e) {
    if (!e.target.matches('[data-extra]')) return;
    var t = render();
    say((e.target.checked ? 'Added ' : 'Removed ') + e.target.dataset.name + '. New total ' + fmt(t.total) + '.');
  });
  render();
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="demo-row">
<div><span class="lbl">Summary with promo code</span>
<section class="card sm" data-summary data-subtotal="387.00" data-ship="25.00" data-tax="8" data-codes="{"SPRING15": {"percent": 15}, "FREESHIP": {"freeShipping": true}}" aria-labelledby="sm1-title"><div class="card-body">
  <h2 class="sm-title" id="sm1-title">Order summary</h2>
  <ul class="list-group">
    <li class="list-group-item sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1503602642458-232111445657?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="badge rounded-pill" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Ash stool</p><p class="sm-meta">Natural oil finish<span class="visually-hidden">, quantity 1</span></p></div><p class="sm-price">$89.00</p></li>
    <li class="list-group-item sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1592078615290-033ee584e267?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="badge rounded-pill" aria-hidden="true">2</span></div>
      <div><p class="sm-name">Shell dining chair</p><p class="sm-meta">Black · Oak legs<span class="visually-hidden">, quantity 2</span></p></div><p class="sm-price">$298.00</p></li>
  </ul>
  <form class="sm-promo" action="#" data-promo>
    <label class="form-label" for="sm1-code">Promo code</label>
    <div class="input-group"><input class="form-control" id="sm1-code" name="promo" type="text" autocomplete="off" spellcheck="false" placeholder="e.g. SPRING15" aria-describedby="sm1-hint sm1-err"><button class="btn sm-apply" type="submit">Apply</button></div>
    <p class="form-text" id="sm1-hint">Try SPRING15 or FREESHIP in this demo.</p>
    <p class="invalid-feedback" id="sm1-err" data-promo-error></p>
    <p class="badge rounded-pill sm-chip" data-applied hidden><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12V4h8l10 10-8 8z"/><circle cx="7.5" cy="8.5" r="1.3"/></svg><span data-applied-code></span><button class="btn" type="button" data-remove-code aria-label="Remove promo code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></p>
  </form>
    <dl class="sm-rows">
      <div class="sm-row"><dt>Subtotal</dt><dd data-subtotal>$387.00</dd></div>
      <div class="sm-row sm-row--save" data-discount-row hidden><dt>Discount (<span data-applied-code></span>)</dt><dd data-discount>−$0.00</dd></div>
      <div class="sm-row"><dt>Shipping</dt><dd data-shipping>$25.00</dd></div>
      <div class="sm-row"><dt>Estimated tax (8%)</dt><dd data-tax>$30.96</dd></div>
    </dl>
    <dl class="sm-total"><dt>Total</dt><dd><small>USD</small><span data-total>$442.96</span></dd></dl>
  <a class="btn btn-primary" href="#"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>Continue to payment</a>
  <p class="visually-hidden" role="status" data-announce></p>
</div></section>
</div>
<div><span class="lbl">Free-shipping progress</span>
<section class="card sm" data-summary data-subtotal="53.00" data-ship="6.50" data-free-over="75" aria-labelledby="sm2-title"><div class="card-body">
  <h2 class="sm-title" id="sm2-title">Your order</h2>
  <div class="sm-free"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 6h12v10H2zM14 9h4l3 3v4h-7"/><circle cx="6" cy="18" r="2"/><circle cx="17" cy="18" r="2"/></svg><p id="sm2-free" data-free-msg>You are $22.00 away from free shipping.</p>
    <div class="progress" role="progressbar" aria-labelledby="sm2-free" aria-valuemin="0" aria-valuemax="100" aria-valuenow="71"><div class="progress-bar" data-free-bar style="width:71%"></div></div></div>
  <ul class="list-group">
    <li class="list-group-item sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1602874801007-bd458bb1b8b6?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="badge rounded-pill" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Cedar and fig candle</p><p class="sm-meta">220 g · 45-hour burn<span class="visually-hidden">, quantity 1</span></p></div><p class="sm-price">$32.00</p></li>
    <li class="list-group-item sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="badge rounded-pill" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Amber room oil</p><p class="sm-meta">30 ml dropper<span class="visually-hidden">, quantity 1</span></p></div><p class="sm-price">$21.00</p></li>
  </ul>
  <label class="form-check sm-addon"><input class="form-check-input" type="checkbox" name="addon" value="travel-tin" data-extra data-name="Travel tin candle" data-price="24.00"><span class="sm-addon-art"><svg width="30" height="30" viewBox="0 0 32 32" aria-hidden="true"><ellipse cx="16" cy="12" rx="11" ry="4" fill="#d6cfc4"/><path d="M5 12v8c0 2.2 4.9 4 11 4s11-1.8 11-4v-8" fill="#b9ae9f"/><ellipse cx="16" cy="12" rx="8" ry="2.6" fill="#efe8dc"/><path d="M16 6v5" stroke="#3b3a36" stroke-width="1.4" stroke-linecap="round"/><path d="M16 3.5c1.4 1.4 1.4 2.6 0 3.6-1.4-1-1.4-2.2 0-3.6z" fill="#e8913a"/></svg></span>
    <span><strong>Add a travel tin candle</strong><span>Same cedar and fig scent, 25-hour burn</span></span><b>+$24.00</b></label>
    <dl class="sm-rows">
      <div class="sm-row"><dt>Subtotal</dt><dd data-subtotal>$53.00</dd></div>
      <div class="sm-row sm-row--save" data-discount-row hidden><dt>Discount (<span data-applied-code></span>)</dt><dd data-discount>−$0.00</dd></div>
      <div class="sm-row"><dt>Shipping</dt><dd data-shipping>$6.50</dd></div>
      
    </dl>
    <dl class="sm-total"><dt>Total</dt><dd><small>USD</small><span data-total>$59.50</span></dd></dl>
  <p class="sm-incl">Taxes included.</p>
  <a class="btn btn-primary" href="#"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>Checkout</a>
  <p class="visually-hidden" role="status" data-announce></p>
</div></section>
</div>
</div>
<div class="demo-narrow"><span class="lbl">Collapsible summary for phones</span>
<details class="card sm sm-details" open>
  <summary>Order summary <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg><b>$442.96</b></summary>
  <div class="card-body">
  <ul class="list-group">
    <li class="list-group-item sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1503602642458-232111445657?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="badge rounded-pill" aria-hidden="true">1</span></div>
      <div><p class="sm-name">Ash stool</p><p class="sm-meta">Natural oil finish<span class="visually-hidden">, quantity 1</span></p></div><p class="sm-price">$89.00</p></li>
    <li class="list-group-item sm-item"><div class="sm-thumb"><img src="https://images.unsplash.com/photo-1592078615290-033ee584e267?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="badge rounded-pill" aria-hidden="true">2</span></div>
      <div><p class="sm-name">Shell dining chair</p><p class="sm-meta">Black · Oak legs<span class="visually-hidden">, quantity 2</span></p></div><p class="sm-price">$298.00</p></li>
  </ul>
    <dl class="sm-rows">
      <div class="sm-row"><dt>Subtotal</dt><dd>$387.00</dd></div>
      <div class="sm-row"><dt>Shipping</dt><dd>$25.00</dd></div>
      <div class="sm-row"><dt>Estimated tax (8%)</dt><dd>$30.96</dd></div>
    </dl>
    <dl class="sm-total"><dt>Total</dt><dd><small>USD</small><span>$442.96</span></dd></dl>
  </div>
</details>
</div>
CSS
/* Bootstrap .card, .list-group, .input-group with .form-control, .is-invalid and .invalid-feedback, .progress,
   .form-check and .badge, recoloured through Bootstrap's CSS variables. The collapsible version is a native <details>. */
.sm{--sm-accent:#0f766e;--sm-accent-dark:#115e59;--sm-ink:#10201e;--sm-muted:#4b5f5c;--sm-line:#dfe7e5;--sm-field:#6b7f7c;--sm-soft:#f0f5f4;--sm-ring:rgba(15,118,110,.3);--sm-error:#b42318;
  --bs-card-border-width:0;--bs-card-border-radius:18px;--bs-card-spacer-x:24px;--bs-card-spacer-y:24px;--bs-body-color:var(--sm-ink);--bs-secondary-color:var(--sm-muted);
  --bs-border-color:var(--sm-field);--bs-border-radius:10px;--bs-form-invalid-border-color:var(--sm-error);--bs-form-invalid-color:var(--sm-error);
  color:var(--sm-ink);font-size:.93rem;line-height:1.45;box-shadow:0 1px 2px rgba(16,32,30,.06),0 18px 40px -24px rgba(16,32,30,.35)}
.sm p,.sm h2,.sm dl,.sm dd,.sm ul{margin:0}
.sm .btn:focus-visible,.sm a:focus-visible,.sm summary:focus-visible{outline:2px solid var(--sm-accent);outline-offset:2px;box-shadow:none}
.sm-title{font-size:1.1rem;font-weight:700;margin-bottom:6px!important}
.sm .list-group{--bs-list-group-border-width:0;--bs-list-group-item-padding-x:0;--bs-list-group-item-padding-y:10px;--bs-list-group-bg:transparent;--bs-list-group-color:var(--sm-ink);
  border-bottom:1px solid var(--sm-line);padding-bottom:6px;border-radius:0}
.sm-item{display:grid!important;grid-template-columns:60px minmax(0,1fr) auto;gap:14px;align-items:center}
.sm-thumb{position:relative;width:60px;height:60px}
.sm-thumb img{width:60px;height:60px;border-radius:12px;object-fit:cover;border:1px solid var(--sm-line);background:var(--sm-soft)}
.sm-thumb .badge{--bs-badge-font-size:.72rem;--bs-badge-padding-x:6px;position:absolute;top:-7px;right:-7px;min-width:22px;height:22px;display:grid;place-items:center;background:var(--sm-muted)}
.sm-name{font-weight:600;line-height:1.3}
.sm-meta{color:var(--sm-muted);font-size:.82rem}
.sm-price{font-weight:600;font-variant-numeric:tabular-nums}
.sm-promo{padding:16px 0;border-bottom:1px solid var(--sm-line)}
.sm .form-label{font-size:.84rem;font-weight:600;margin-bottom:6px}
.sm .input-group{gap:8px}
.sm .input-group>.form-control{border-radius:10px!important;padding:10px 12px;font-size:inherit;text-transform:uppercase;letter-spacing:.04em;background-image:none}
.sm .form-control::placeholder{text-transform:none;letter-spacing:0;color:#64748b}
.sm .form-control:focus{border-color:var(--sm-accent);box-shadow:0 0 0 3px var(--sm-ring)}
.sm .form-control.is-invalid{padding-right:12px}
.sm .form-control.is-invalid:focus{box-shadow:0 0 0 3px rgba(180,35,24,.2)}
.sm .input-group>.btn{border-radius:10px!important;margin-left:0!important}
.sm-apply{--bs-btn-bg:var(--sm-ink);--bs-btn-border-color:var(--sm-ink);--bs-btn-color:#fff;--bs-btn-hover-bg:#25403c;--bs-btn-hover-border-color:#25403c;--bs-btn-hover-color:#fff;
  --bs-btn-active-bg:#25403c;--bs-btn-active-color:#fff;--bs-btn-font-weight:700;--bs-btn-padding-x:16px}
.sm .form-text{font-size:.78rem;color:var(--sm-muted);margin-top:6px}
.sm .invalid-feedback{display:flex;gap:6px;font-size:.8rem;font-weight:600;margin-top:6px}
.sm .invalid-feedback::before{content:"!";flex:none;width:16px;height:16px;margin-top:1px;border-radius:50%;background:var(--sm-error);color:#fff;font-size:.7rem;font-weight:800;display:grid;place-items:center}
.sm .invalid-feedback:empty{display:none}
.sm-chip{--bs-badge-font-size:.8rem;--bs-badge-font-weight:700;--bs-badge-color:var(--sm-accent-dark);display:inline-flex;align-items:center;gap:6px;margin-top:10px;padding:4px 4px 4px 10px;
  background:#e3f1ef;letter-spacing:.03em}
.sm-chip .btn{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;--bs-btn-color:inherit;--bs-btn-hover-color:inherit;--bs-btn-hover-bg:#c9e6e2;width:24px;height:24px;display:grid;place-items:center;border-radius:50%}
.sm-rows{display:grid;gap:8px;padding:16px 0;border-bottom:1px solid var(--sm-line)}
.sm-row{display:flex;justify-content:space-between;gap:12px}
.sm-row dt{color:var(--sm-muted);font-weight:400}
.sm-row dd{font-weight:600;font-variant-numeric:tabular-nums;text-align:right}
.sm-row--save dt,.sm-row--save dd{color:var(--sm-accent);font-weight:700}
.sm-total{display:flex;justify-content:space-between;align-items:baseline;padding-top:16px}
.sm-total dt{font-weight:700;font-size:1.05rem}
.sm-total dd{font-weight:800;font-size:1.4rem;font-variant-numeric:tabular-nums}
.sm-total small{font-size:.75rem;font-weight:600;color:var(--sm-muted);margin-right:6px}
.sm .btn-primary{--bs-btn-bg:var(--sm-accent);--bs-btn-border-color:var(--sm-accent);--bs-btn-hover-bg:var(--sm-accent-dark);--bs-btn-hover-border-color:var(--sm-accent-dark);
  --bs-btn-active-bg:var(--sm-accent-dark);--bs-btn-active-border-color:var(--sm-accent-dark);--bs-btn-padding-y:12px;--bs-btn-font-weight:700;--bs-btn-border-radius:12px;
  display:flex;align-items:center;justify-content:center;gap:8px;width:100%;margin-top:18px}
.sm-free{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;align-items:center;padding:14px 16px;border-radius:14px;background:var(--sm-soft);margin:12px 0 4px}
.sm-free svg{grid-row:span 2;color:var(--sm-accent)}
.sm-free p{font-weight:600;font-size:.88rem}
.sm .progress{--bs-progress-height:8px;--bs-progress-bg:#cfdcd9;--bs-progress-border-radius:99px;--bs-progress-bar-bg:var(--sm-accent);--bs-progress-bar-transition:width .4s}
.sm .progress-bar{background:linear-gradient(90deg,#14b8a6,var(--sm-accent))}
.sm-addon{display:grid!important;grid-template-columns:auto 40px minmax(0,1fr) auto;gap:12px;align-items:center;margin:12px 0 0;padding:12px 14px!important;border:1px dashed var(--sm-field);border-radius:14px;cursor:pointer}
.sm-addon:has(:checked){border-style:solid;border-color:var(--sm-accent);background:#f3faf9}
.sm-addon .form-check-input{float:none;margin:0;width:20px;height:20px;border:1.5px solid var(--sm-field);border-radius:6px;cursor:pointer}
.sm-addon .form-check-input:checked{background-color:var(--sm-accent);border-color:var(--sm-accent)}
.sm-addon .form-check-input:focus{box-shadow:none;border-color:var(--sm-field)}
.sm-addon .form-check-input:focus-visible{outline:2px solid var(--sm-accent);outline-offset:2px}
.sm-addon-art{width:40px;height:40px;border-radius:10px;background:#f6f1ea;display:grid;place-items:center}
.sm-addon strong{display:block;font-size:.88rem}
.sm-addon span{font-size:.8rem;color:var(--sm-muted)}
.sm-addon b{font-variant-numeric:tabular-nums}
.sm-incl{font-size:.78rem;color:var(--sm-muted);margin-top:4px!important;text-align:right}
.sm-details{--bs-card-spacer-x:0;--bs-card-spacer-y:0;overflow:hidden}
.sm-details summary{display:flex;align-items:center;gap:8px;padding:16px 20px;cursor:pointer;list-style:none;background:var(--sm-soft);color:var(--sm-accent-dark);font-weight:700}
.sm-details summary::-webkit-details-marker{display:none}
.sm-details summary svg{transition:transform .2s}
.sm-details[open] summary svg{transform:rotate(180deg)}
.sm-details summary b{margin-left:auto;color:var(--sm-ink);font-size:1.1rem;font-variant-numeric:tabular-nums}
.sm-details summary:focus-visible{outline-offset:-3px}
.sm-details .card-body{padding:8px 20px 20px}
@media (max-width:420px){.sm{--bs-card-spacer-x:18px;--bs-card-spacer-y:20px}}
@media (prefers-reduced-motion:reduce){.sm .progress-bar,.sm .btn,.sm-details summary svg{transition:none}}
JavaScript
// Order summary: recalculates discount, shipping, tax and total in cents when a promo code is applied or
// removed, or an add-on is ticked. Codes live in data-codes on each [data-summary]; they are checked in the
// browser for this demo only, so check them again on the server before charging anything.
document.querySelectorAll('[data-summary]').forEach(function (box) {
  var money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
  var cents = function (v) { return Math.round(parseFloat(v) * 100); };
  var fmt = function (c) { return money.format(c / 100); };
  var codes = JSON.parse(box.dataset.codes || '{}'), rate = parseFloat(box.dataset.tax || 0);
  var freeOver = box.dataset.freeOver ? cents(box.dataset.freeOver) : Infinity, fee = cents(box.dataset.ship || 0);
  var status = box.querySelector('[data-announce]'), applied = '';
  function put(sel, text) { box.querySelectorAll(sel).forEach(function (el) { el.textContent = text; }); }
  function say(text) { if (status) status.textContent = text; }
  function render() {
    var sub = cents(box.dataset.subtotal);
    box.querySelectorAll('[data-extra]:checked').forEach(function (x) { sub += cents(x.dataset.price); });
    var code = codes[applied] || {};
    var off = code.percent ? Math.round(sub * code.percent / 100) : 0;
    var ship = code.freeShipping || sub >= freeOver ? 0 : fee;
    var tax = Math.round((sub - off) * rate / 100), total = sub - off + ship + tax;
    put('[data-subtotal]', fmt(sub)); put('[data-discount]', '−' + fmt(off)); put('[data-tax]', fmt(tax));
    put('[data-shipping]', ship ? fmt(ship) : 'Free'); put('[data-total]', fmt(total)); put('[data-applied-code]', applied);
    box.querySelectorAll('[data-discount-row]').forEach(function (el) { el.hidden = !off; });
    box.querySelectorAll('[data-applied]').forEach(function (el) { el.hidden = !applied; });
    put('[data-free-msg]', sub >= freeOver ? 'You have unlocked free shipping.' : 'You are ' + fmt(freeOver - sub) + ' away from free shipping.');
    box.querySelectorAll('[data-free-bar]').forEach(function (bar) {
      var pct = Math.min(100, Math.round(sub / freeOver * 100));
      bar.style.width = pct + '%'; bar.parentElement.setAttribute('aria-valuenow', pct);
    });
    return { off: off, ship: ship, total: total };
  }
  var form = box.querySelector('[data-promo]');
  if (form) {
    var input = form.querySelector('input'), err = form.querySelector('[data-promo-error]');
    form.noValidate = true;
    form.addEventListener('submit', function (e) {
      e.preventDefault(); // demo only: nothing is sent
      var code = input.value.trim().toUpperCase();
      var msg = !code ? 'Enter a promo code.' : codes[code] ? '' : code + ' is not a valid code. Check the spelling and try again.';
      err.textContent = msg; input.classList.toggle('is-invalid', !!msg);
      if (msg) { input.setAttribute('aria-invalid', 'true'); input.focus(); return; }
      input.removeAttribute('aria-invalid'); input.value = ''; applied = code;
      var t = render();
      say('Promo code ' + code + ' applied. ' + (t.off ? 'You save ' + fmt(t.off) + '. ' : 'Shipping is free. ') + 'New total ' + fmt(t.total) + '.');
    });
    input.addEventListener('input', function () { if (err.textContent) { err.textContent = ''; input.classList.remove('is-invalid'); input.removeAttribute('aria-invalid'); } });
  }
  box.addEventListener('click', function (e) {
    if (!e.target.closest('[data-remove-code]')) return;
    applied = ''; var t = render();
    say('Promo code removed. New total ' + fmt(t.total) + '.');
    if (form) form.querySelector('input').focus();
  });
  box.addEventListener('change', function (e) {
    if (!e.target.matches('[data-extra]')) return;
    var t = render();
    say((e.target.checked ? 'Added ' : 'Removed ') + e.target.dataset.name + '. New total ' + fmt(t.total) + '.');
  });
  render();
});

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] items-start gap-[34px]">
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4a5a57] uppercase">Summary with promo code</span>
<section class="rounded-[18px] bg-white text-[.93rem] leading-[1.45] text-sm-ink shadow-[0_1px_2px_rgba(16,32,30,.06),0_18px_40px_-24px_rgba(16,32,30,.35)] p-6 max-[420px]:px-[18px] max-[420px]:py-5" data-summary data-subtotal="387.00" data-ship="25.00" data-tax="8" data-codes="{"SPRING15": {"percent": 15}, "FREESHIP": {"freeShipping": true}}" aria-labelledby="sm1-title">
  <h2 class="mb-1.5 text-[1.1rem] font-bold" id="sm1-title">Order summary</h2>
  <ul class="border-b border-sm-line pb-1.5">
    <li class="grid grid-cols-[60px_minmax(0,1fr)_auto] items-center gap-3.5 py-2.5"><div class="relative size-[60px]"><img class="size-[60px] rounded-xl border border-sm-line bg-sm-soft object-cover" src="https://images.unsplash.com/photo-1503602642458-232111445657?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="absolute -top-[7px] -right-[7px] grid h-[22px] min-w-[22px] place-items-center rounded-full bg-sm-muted px-1.5 text-[.72rem] font-bold text-white" aria-hidden="true">1</span></div>
      <div><p class="leading-[1.3] font-semibold">Ash stool</p><p class="text-[.82rem] text-sm-muted">Natural oil finish<span class="sr-only">, quantity 1</span></p></div><p class="font-semibold tabular-nums">$89.00</p></li>
    <li class="grid grid-cols-[60px_minmax(0,1fr)_auto] items-center gap-3.5 py-2.5"><div class="relative size-[60px]"><img class="size-[60px] rounded-xl border border-sm-line bg-sm-soft object-cover" src="https://images.unsplash.com/photo-1592078615290-033ee584e267?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="absolute -top-[7px] -right-[7px] grid h-[22px] min-w-[22px] place-items-center rounded-full bg-sm-muted px-1.5 text-[.72rem] font-bold text-white" aria-hidden="true">2</span></div>
      <div><p class="leading-[1.3] font-semibold">Shell dining chair</p><p class="text-[.82rem] text-sm-muted">Black · Oak legs<span class="sr-only">, quantity 2</span></p></div><p class="font-semibold tabular-nums">$298.00</p></li>
  </ul>
  <form class="border-b border-sm-line py-4" action="#" data-promo>
    <label class="mb-1.5 block text-[.84rem] font-semibold" for="sm1-code">Promo code</label>
    <div class="flex gap-2"><input class="min-w-0 flex-1 rounded-[10px] border border-sm-field bg-white px-3 py-2.5 tracking-[.04em] uppercase placeholder:tracking-normal placeholder:text-[#64748b] placeholder:normal-case focus:border-sm-accent focus:shadow-[0_0_0_3px_rgba(15,118,110,.3)] focus:outline-none aria-invalid:border-sm-error aria-invalid:focus:shadow-[0_0_0_3px_rgba(180,35,24,.2)]" id="sm1-code" name="promo" type="text" autocomplete="off" spellcheck="false" placeholder="e.g. SPRING15" aria-describedby="sm1-hint sm1-err"><button class="cursor-pointer rounded-[10px] border border-sm-ink bg-sm-ink px-4 py-2.5 font-bold text-white transition-colors duration-150 hover:bg-[#25403c] motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sm-accent" type="submit">Apply</button></div>
    <p class="mt-1.5 text-[.78rem] text-sm-muted" id="sm1-hint">Try SPRING15 or FREESHIP in this demo.</p>
    <p class="mt-1.5 flex gap-1.5 text-[.8rem] font-semibold text-sm-error empty:hidden before:mt-px before:grid before:size-4 before:flex-none before:place-items-center before:rounded-full before:bg-sm-error before:text-[.7rem] before:font-extrabold before:text-white before:content-['!']" id="sm1-err" data-promo-error></p>
    <p class="mt-2.5 inline-flex items-center gap-1.5 rounded-full bg-[#e3f1ef] py-1 pr-1 pl-2.5 text-[.8rem] font-bold tracking-[.03em] text-sm-accent-dark" data-applied hidden><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 12V4h8l10 10-8 8z"/><circle cx="7.5" cy="8.5" r="1.3"/></svg><span data-applied-code></span><button class="grid size-6 cursor-pointer place-items-center rounded-full hover:bg-[#c9e6e2] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sm-accent" type="button" data-remove-code aria-label="Remove promo code"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></p>
  </form>
    <dl class="grid gap-2 border-b border-sm-line py-4">
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Subtotal</dt><dd class="text-right font-semibold tabular-nums" data-subtotal>$387.00</dd></div>
      <div class="flex justify-between gap-3 font-bold text-sm-accent" data-discount-row hidden><dt>Discount (<span data-applied-code></span>)</dt><dd class="text-right tabular-nums" data-discount>−$0.00</dd></div>
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Shipping</dt><dd class="text-right font-semibold tabular-nums" data-shipping>$25.00</dd></div>
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Estimated tax (8%)</dt><dd class="text-right font-semibold tabular-nums" data-tax>$30.96</dd></div>
    </dl>
    <dl class="flex items-baseline justify-between pt-4"><dt class="text-[1.05rem] font-bold">Total</dt><dd class="text-[1.4rem] font-extrabold tabular-nums"><small class="mr-1.5 text-[.75rem] font-semibold text-sm-muted">USD</small><span data-total>$442.96</span></dd></dl>
  <a class="mt-[18px] flex w-full items-center justify-center gap-2 rounded-xl bg-sm-accent px-[18px] py-[13px] font-bold text-white transition-colors duration-150 hover:bg-sm-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sm-accent" href="#"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>Continue to payment</a>
  <p class="sr-only" role="status" data-announce></p>
</section>
</div>
<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4a5a57] uppercase">Free-shipping progress</span>
<section class="rounded-[18px] bg-white text-[.93rem] leading-[1.45] text-sm-ink shadow-[0_1px_2px_rgba(16,32,30,.06),0_18px_40px_-24px_rgba(16,32,30,.35)] p-6 max-[420px]:px-[18px] max-[420px]:py-5" data-summary data-subtotal="53.00" data-ship="6.50" data-free-over="75" aria-labelledby="sm2-title">
  <h2 class="mb-1.5 text-[1.1rem] font-bold" id="sm2-title">Your order</h2>
  <div class="mt-3 mb-1 grid grid-cols-[auto_1fr] items-center gap-x-3 gap-y-1 rounded-[14px] bg-sm-soft px-4 py-3.5 [&>svg]:row-span-2 [&>svg]:text-sm-accent"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 6h12v10H2zM14 9h4l3 3v4h-7"/><circle cx="6" cy="18" r="2"/><circle cx="17" cy="18" r="2"/></svg><p class="text-[.88rem] font-semibold" id="sm2-free" data-free-msg>You are $22.00 away from free shipping.</p>
    <div class="h-2 overflow-hidden rounded-full bg-[#cfdcd9]" role="progressbar" aria-labelledby="sm2-free" aria-valuemin="0" aria-valuemax="100" aria-valuenow="71"><span class="block h-full rounded-[inherit] bg-linear-to-r from-[#14b8a6] to-sm-accent transition-[width] duration-400 motion-reduce:transition-none" data-free-bar style="width:71%"></span></div></div>
  <ul class="border-b border-sm-line pb-1.5">
    <li class="grid grid-cols-[60px_minmax(0,1fr)_auto] items-center gap-3.5 py-2.5"><div class="relative size-[60px]"><img class="size-[60px] rounded-xl border border-sm-line bg-sm-soft object-cover" src="https://images.unsplash.com/photo-1602874801007-bd458bb1b8b6?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="absolute -top-[7px] -right-[7px] grid h-[22px] min-w-[22px] place-items-center rounded-full bg-sm-muted px-1.5 text-[.72rem] font-bold text-white" aria-hidden="true">1</span></div>
      <div><p class="leading-[1.3] font-semibold">Cedar and fig candle</p><p class="text-[.82rem] text-sm-muted">220 g · 45-hour burn<span class="sr-only">, quantity 1</span></p></div><p class="font-semibold tabular-nums">$32.00</p></li>
    <li class="grid grid-cols-[60px_minmax(0,1fr)_auto] items-center gap-3.5 py-2.5"><div class="relative size-[60px]"><img class="size-[60px] rounded-xl border border-sm-line bg-sm-soft object-cover" src="https://images.unsplash.com/photo-1608571423902-eed4a5ad8108?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="absolute -top-[7px] -right-[7px] grid h-[22px] min-w-[22px] place-items-center rounded-full bg-sm-muted px-1.5 text-[.72rem] font-bold text-white" aria-hidden="true">1</span></div>
      <div><p class="leading-[1.3] font-semibold">Amber room oil</p><p class="text-[.82rem] text-sm-muted">30 ml dropper<span class="sr-only">, quantity 1</span></p></div><p class="font-semibold tabular-nums">$21.00</p></li>
  </ul>
  <label class="mt-3 grid cursor-pointer grid-cols-[auto_40px_minmax(0,1fr)_auto] items-center gap-3 rounded-[14px] border border-dashed border-sm-field px-3.5 py-3 has-checked:border-solid has-checked:border-sm-accent has-checked:bg-[#f3faf9]"><input class="size-5 cursor-pointer appearance-none rounded-md border-[1.5px] border-sm-field bg-white bg-center bg-[length:13px] bg-no-repeat checked:border-sm-accent checked:bg-sm-accent checked:sm-tick focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sm-accent" type="checkbox" name="addon" value="travel-tin" data-extra data-name="Travel tin candle" data-price="24.00"><span class="grid size-10 place-items-center rounded-[10px] bg-[#f6f1ea]"><svg width="30" height="30" viewBox="0 0 32 32" aria-hidden="true"><ellipse cx="16" cy="12" rx="11" ry="4" fill="#d6cfc4"/><path d="M5 12v8c0 2.2 4.9 4 11 4s11-1.8 11-4v-8" fill="#b9ae9f"/><ellipse cx="16" cy="12" rx="8" ry="2.6" fill="#efe8dc"/><path d="M16 6v5" stroke="#3b3a36" stroke-width="1.4" stroke-linecap="round"/><path d="M16 3.5c1.4 1.4 1.4 2.6 0 3.6-1.4-1-1.4-2.2 0-3.6z" fill="#e8913a"/></svg></span>
    <span><strong class="block text-[.88rem]">Add a travel tin candle</strong><span class="text-[.8rem] text-sm-muted">Same cedar and fig scent, 25-hour burn</span></span><b class="tabular-nums">+$24.00</b></label>
    <dl class="grid gap-2 border-b border-sm-line py-4">
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Subtotal</dt><dd class="text-right font-semibold tabular-nums" data-subtotal>$53.00</dd></div>
      <div class="flex justify-between gap-3 font-bold text-sm-accent" data-discount-row hidden><dt>Discount (<span data-applied-code></span>)</dt><dd class="text-right tabular-nums" data-discount>−$0.00</dd></div>
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Shipping</dt><dd class="text-right font-semibold tabular-nums" data-shipping>$6.50</dd></div>
      
    </dl>
    <dl class="flex items-baseline justify-between pt-4"><dt class="text-[1.05rem] font-bold">Total</dt><dd class="text-[1.4rem] font-extrabold tabular-nums"><small class="mr-1.5 text-[.75rem] font-semibold text-sm-muted">USD</small><span data-total>$59.50</span></dd></dl>
  <p class="mt-1 text-right text-[.78rem] text-sm-muted">Taxes included.</p>
  <a class="mt-[18px] flex w-full items-center justify-center gap-2 rounded-xl bg-sm-accent px-[18px] py-[13px] font-bold text-white transition-colors duration-150 hover:bg-sm-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-sm-accent" href="#"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>Checkout</a>
  <p class="sr-only" role="status" data-announce></p>
</section>
</div>
</div>
<div class="max-w-[453px]"><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4a5a57] uppercase">Collapsible summary for phones</span>
<details class="group overflow-hidden rounded-[18px] bg-white text-[.93rem] leading-[1.45] text-sm-ink shadow-[0_1px_2px_rgba(16,32,30,.06),0_18px_40px_-24px_rgba(16,32,30,.35)]" open>
  <summary class="flex cursor-pointer list-none items-center gap-2 bg-sm-soft px-5 py-4 font-bold text-sm-accent-dark focus-visible:outline-2 focus-visible:-outline-offset-[3px] focus-visible:outline-sm-accent [&::-webkit-details-marker]:hidden [&>svg]:transition-transform [&>svg]:duration-200 group-open:[&>svg]:rotate-180 motion-reduce:[&>svg]:transition-none">Order summary <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6l4 4 4-4"/></svg><b class="ml-auto text-[1.1rem] text-sm-ink tabular-nums">$442.96</b></summary>
  <div class="px-5 pt-2 pb-5">
  <ul class="border-b border-sm-line pb-1.5">
    <li class="grid grid-cols-[60px_minmax(0,1fr)_auto] items-center gap-3.5 py-2.5"><div class="relative size-[60px]"><img class="size-[60px] rounded-xl border border-sm-line bg-sm-soft object-cover" src="https://images.unsplash.com/photo-1503602642458-232111445657?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="absolute -top-[7px] -right-[7px] grid h-[22px] min-w-[22px] place-items-center rounded-full bg-sm-muted px-1.5 text-[.72rem] font-bold text-white" aria-hidden="true">1</span></div>
      <div><p class="leading-[1.3] font-semibold">Ash stool</p><p class="text-[.82rem] text-sm-muted">Natural oil finish<span class="sr-only">, quantity 1</span></p></div><p class="font-semibold tabular-nums">$89.00</p></li>
    <li class="grid grid-cols-[60px_minmax(0,1fr)_auto] items-center gap-3.5 py-2.5"><div class="relative size-[60px]"><img class="size-[60px] rounded-xl border border-sm-line bg-sm-soft object-cover" src="https://images.unsplash.com/photo-1592078615290-033ee584e267?w=120&h=120&fit=crop&auto=format&q=70" alt="" width="60" height="60"><span class="absolute -top-[7px] -right-[7px] grid h-[22px] min-w-[22px] place-items-center rounded-full bg-sm-muted px-1.5 text-[.72rem] font-bold text-white" aria-hidden="true">2</span></div>
      <div><p class="leading-[1.3] font-semibold">Shell dining chair</p><p class="text-[.82rem] text-sm-muted">Black · Oak legs<span class="sr-only">, quantity 2</span></p></div><p class="font-semibold tabular-nums">$298.00</p></li>
  </ul>
    <dl class="grid gap-2 border-b border-sm-line py-4">
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Subtotal</dt><dd class="text-right font-semibold tabular-nums">$387.00</dd></div>
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Shipping</dt><dd class="text-right font-semibold tabular-nums">$25.00</dd></div>
      <div class="flex justify-between gap-3"><dt class="text-sm-muted">Estimated tax (8%)</dt><dd class="text-right font-semibold tabular-nums">$30.96</dd></div>
    </dl>
    <dl class="flex items-baseline justify-between pt-4"><dt class="text-[1.05rem] font-bold">Total</dt><dd class="text-[1.4rem] font-extrabold tabular-nums"><small class="mr-1.5 text-[.75rem] font-semibold text-sm-muted">USD</small><span>$442.96</span></dd></dl>
  </div>
</details>
</div>
CSS
@theme {
  --color-sm-accent: #0f766e;
  --color-sm-accent-dark: #115e59;
  --color-sm-ink: #10201e;
  --color-sm-muted: #4b5f5c;
  --color-sm-line: #dfe7e5;
  --color-sm-field: #6b7f7c;
  --color-sm-soft: #f0f5f4;
  --color-sm-error: #b42318;
}
/* white tick for the checked add-on box */
@utility sm-tick {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-7' fill='none' stroke='%23fff' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
JavaScript
// Order summary: recalculates discount, shipping, tax and total in cents when a promo code is applied or
// removed, or an add-on is ticked. Codes live in data-codes on each [data-summary]; they are checked in the
// browser for this demo only, so check them again on the server before charging anything.
document.querySelectorAll('[data-summary]').forEach(function (box) {
  var money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
  var cents = function (v) { return Math.round(parseFloat(v) * 100); };
  var fmt = function (c) { return money.format(c / 100); };
  var codes = JSON.parse(box.dataset.codes || '{}'), rate = parseFloat(box.dataset.tax || 0);
  var freeOver = box.dataset.freeOver ? cents(box.dataset.freeOver) : Infinity, fee = cents(box.dataset.ship || 0);
  var status = box.querySelector('[data-announce]'), applied = '';
  function put(sel, text) { box.querySelectorAll(sel).forEach(function (el) { el.textContent = text; }); }
  function say(text) { if (status) status.textContent = text; }
  function render() {
    var sub = cents(box.dataset.subtotal);
    box.querySelectorAll('[data-extra]:checked').forEach(function (x) { sub += cents(x.dataset.price); });
    var code = codes[applied] || {};
    var off = code.percent ? Math.round(sub * code.percent / 100) : 0;
    var ship = code.freeShipping || sub >= freeOver ? 0 : fee;
    var tax = Math.round((sub - off) * rate / 100), total = sub - off + ship + tax;
    put('[data-subtotal]', fmt(sub)); put('[data-discount]', '−' + fmt(off)); put('[data-tax]', fmt(tax));
    put('[data-shipping]', ship ? fmt(ship) : 'Free'); put('[data-total]', fmt(total)); put('[data-applied-code]', applied);
    box.querySelectorAll('[data-discount-row]').forEach(function (el) { el.hidden = !off; });
    box.querySelectorAll('[data-applied]').forEach(function (el) { el.hidden = !applied; });
    put('[data-free-msg]', sub >= freeOver ? 'You have unlocked free shipping.' : 'You are ' + fmt(freeOver - sub) + ' away from free shipping.');
    box.querySelectorAll('[data-free-bar]').forEach(function (bar) {
      var pct = Math.min(100, Math.round(sub / freeOver * 100));
      bar.style.width = pct + '%'; bar.parentElement.setAttribute('aria-valuenow', pct);
    });
    return { off: off, ship: ship, total: total };
  }
  var form = box.querySelector('[data-promo]');
  if (form) {
    var input = form.querySelector('input'), err = form.querySelector('[data-promo-error]');
    form.noValidate = true;
    form.addEventListener('submit', function (e) {
      e.preventDefault(); // demo only: nothing is sent
      var code = input.value.trim().toUpperCase();
      var msg = !code ? 'Enter a promo code.' : codes[code] ? '' : code + ' is not a valid code. Check the spelling and try again.';
      err.textContent = msg;
      if (msg) { input.setAttribute('aria-invalid', 'true'); input.focus(); return; }
      input.removeAttribute('aria-invalid'); input.value = ''; applied = code;
      var t = render();
      say('Promo code ' + code + ' applied. ' + (t.off ? 'You save ' + fmt(t.off) + '. ' : 'Shipping is free. ') + 'New total ' + fmt(t.total) + '.');
    });
    input.addEventListener('input', function () { if (err.textContent) { err.textContent = ''; input.removeAttribute('aria-invalid'); } });
  }
  box.addEventListener('click', function (e) {
    if (!e.target.closest('[data-remove-code]')) return;
    applied = ''; var t = render();
    say('Promo code removed. New total ' + fmt(t.total) + '.');
    if (form) form.querySelector('input').focus();
  });
  box.addEventListener('change', function (e) {
    if (!e.target.matches('[data-extra]')) return;
    var t = render();
    say((e.target.checked ? 'Added ' : 'Removed ') + e.target.dataset.name + '. New total ' + fmt(t.total) + '.');
  });
  render();
});

About this set

Three order summary panels for the side of a cart or checkout. Summary with promo code is a furniture order with thumbnails carrying a quantity bubble, a promo code field with an Apply button, and subtotal, discount, shipping, estimated tax and total lines. The demo accepts SPRING15 for 15 percent off and FREESHIP for free shipping; an applied code shows as a chip with a remove button and a discount line appears, while an unknown code gets an error message tied to the field. Free-shipping progress is a candle order with a bar showing how far the subtotal is from free shipping and an add-on card; ticking the travel tin candle adds $24.00, fills the bar and switches shipping to free. Collapsible summary for phones is a native details element whose summary row shows the total, so the full breakdown can fold away above a mobile checkout. A short script recalculates discount, shipping, tax and total in cents from data attributes on each panel, so the figures always add up, and announces each change politely. Codes are checked in the browser for the demo only, so check them again on the server.

What’s included

  • Promo code field with apply, remove, inline error and discount line
  • Discount, shipping, tax and total recalculated in cents
  • Free-shipping progress bar with an add-on checkbox card
  • Collapsible summary built on details and summary, no script
  • Quantity bubbles on the product thumbnails

Accessibility

The promo field has a visible label and a hint, and its error message is linked through aria-describedby; the input gets aria-invalid while the message shows and focus returns to it. The applied code chip has a remove button named Remove promo code, quantities are given as visually hidden text, and the shipping bar is a labelled progressbar. Totals are announced through a polite status region, and transitions switch off under prefers-reduced-motion.

Customise it

Change --sm-accent, --sm-line and --sm-soft on .sm. Subtotal, shipping fee, tax rate, free-shipping threshold and promo codes are the data-subtotal, data-ship, data-tax, data-free-over and data-codes attributes. In Tailwind edit the sm-* colours in @theme.