Component Library

← Back to gallery

Order summary

Category: Tables & lists · Tags: checkout, order, cart, totals, discount

Order summary

3 items
ItemQtyAmount
Ceramic pour-over set1$42.00
Paper filters, pack of 1002$13.00
Medium roast beans, 500 g1$18.50
Subtotal$73.50
Shipping$5.00
Total$78.50

Try SAVE10 for 10% off.

How to use it

Use beside a checkout form to confirm what is being bought and what it will cost. For an editable cart with quantity controls and remove buttons, use a full cart table instead. Fill the rows and data-cents values from your order, check discount codes on the server (the in-page SAVE10 code is only a demo), and change the currency formatting to match your store.

Accessibility

The items and totals are one native <table> with a caption, <th scope="col"> column headings and <th scope="row"> row headings, so every amount is read with its item or total name. The discount code has its own <label for>, Apply is a native submit <button>, and the result (or an error, with aria-invalid on the field) is announced through a polite live region linked by aria-describedby. Text is #0f172a on white (17.85:1), muted text #475569 on white (7.58:1), the discount row #166534 on white (7.13:1), errors #b91c1c on white (6.47:1) and Apply white on #1d4ed8 (6.70:1, 8.72:1 hovered), with a 2px #1d4ed8 focus outline.

Source (copy-ready)

<section class="dcl-order" aria-labelledby="dcl-order-title">
  <h2 class="dcl-order__title" id="dcl-order-title">Order summary</h2>
  <div class="dcl-order__scroll">
    <table class="dcl-order__table">
      <caption class="dcl-order__caption">3 items</caption>
      <thead>
        <tr><th scope="col" class="dcl-order__head">Item</th><th scope="col" class="dcl-order__head dcl-order__num">Qty</th><th scope="col" class="dcl-order__head dcl-order__num">Amount</th></tr>
      </thead>
      <tbody>
        <tr><th scope="row" class="dcl-order__item">Ceramic pour-over set</th><td class="dcl-order__num">1</td><td class="dcl-order__num dcl-order__line" data-cents="4200">$42.00</td></tr>
        <tr><th scope="row" class="dcl-order__item">Paper filters, pack of 100</th><td class="dcl-order__num">2</td><td class="dcl-order__num dcl-order__line" data-cents="1300">$13.00</td></tr>
        <tr><th scope="row" class="dcl-order__item">Medium roast beans, 500 g</th><td class="dcl-order__num">1</td><td class="dcl-order__num dcl-order__line" data-cents="1850">$18.50</td></tr>
      </tbody>
      <tfoot>
        <tr><th scope="row" colspan="2" class="dcl-order__label">Subtotal</th><td class="dcl-order__num dcl-order__subtotal">$73.50</td></tr>
        <tr class="dcl-order__discount-row" hidden><th scope="row" colspan="2" class="dcl-order__label dcl-order__saving">Discount</th><td class="dcl-order__num dcl-order__saving dcl-order__discount">-$0.00</td></tr>
        <tr><th scope="row" colspan="2" class="dcl-order__label">Shipping</th><td class="dcl-order__num dcl-order__shipping" data-cents="500">$5.00</td></tr>
        <tr><th scope="row" colspan="2" class="dcl-order__label dcl-order__grand">Total</th><td class="dcl-order__num dcl-order__grand dcl-order__total">$78.50</td></tr>
      </tfoot>
    </table>
  </div>
  <form class="dcl-order__form">
    <label class="dcl-order__code-label" for="dcl-order-code">Discount code</label>
    <div class="dcl-order__code-row">
      <input class="dcl-order__input" type="text" id="dcl-order-code" name="discount-code" autocomplete="off" spellcheck="false" aria-describedby="dcl-order-message">
      <button type="submit" class="dcl-order__apply">Apply</button>
    </div>
    <p class="dcl-order__message" id="dcl-order-message" aria-live="polite">Try SAVE10 for 10% off.</p>
  </form>
</section>
<style>
.dcl-order { max-width: 26rem; padding: 1rem; border: 1px solid #cbd5e1; border-radius: 0.5rem; font: inherit; color: #0f172a; background-color: #ffffff; }
.dcl-order__title { margin: 0 0 0.5rem; font-size: 1.15rem; color: #0f172a; background-color: #ffffff; }
.dcl-order__scroll { max-width: 100%; overflow-x: auto; }
.dcl-order__table { width: 100%; border-collapse: collapse; }
.dcl-order__caption { padding-bottom: 0.4rem; text-align: left; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-order__head { padding: 0.4rem 0.25rem; border-bottom: 1px solid #cbd5e1; font-size: 0.85rem; text-align: left; color: #475569; background-color: #ffffff; }
.dcl-order__item, .dcl-order__label { padding: 0.45rem 0.25rem; font-weight: 400; text-align: left; color: #0f172a; background-color: #ffffff; }
.dcl-order__table td { padding: 0.45rem 0.25rem; color: #0f172a; background-color: #ffffff; }
.dcl-order__num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }
.dcl-order__table tfoot tr:first-child th, .dcl-order__table tfoot tr:first-child td { border-top: 1px solid #cbd5e1; }
.dcl-order__table .dcl-order__saving { color: #166534; background-color: #ffffff; }
.dcl-order__table .dcl-order__grand { border-top: 2px solid #0f172a; font-weight: 700; font-size: 1.05rem; color: #0f172a; background-color: #ffffff; }
.dcl-order__form { margin-top: 1rem; }
.dcl-order__code-label { display: block; margin-bottom: 0.3rem; font-weight: 600; color: #0f172a; background-color: #ffffff; }
.dcl-order__code-row { display: flex; gap: 0.5rem; }
.dcl-order__input { box-sizing: border-box; flex: 1 1 auto; min-width: 0; padding: 0.45rem 0.6rem; border: 1px solid #64748b; border-radius: 6px; font: inherit; text-transform: uppercase; color: #0f172a; background-color: #ffffff; }
.dcl-order__input[aria-invalid="true"] { border: 2px solid #b91c1c; }
.dcl-order__input:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 1px; }
.dcl-order__apply { padding: 0.45rem 1rem; border: none; border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer; color: #ffffff; background-color: #1d4ed8; }
.dcl-order__apply:hover { color: #ffffff; background-color: #1e40af; }
.dcl-order__apply:focus-visible { outline: 2px solid #1d4ed8; outline-offset: 2px; }
.dcl-order__message { margin: 0.4rem 0 0; font-size: 0.9rem; color: #475569; background-color: #ffffff; }
.dcl-order__message.dcl-order__message--error { color: #b91c1c; background-color: #ffffff; }
</style>
<script>
(function () {
  var codes = { SAVE10: 10 };
  function money(cents) { return "$" + (cents / 100).toFixed(2); }
  document.querySelectorAll(".dcl-order").forEach(function (root) {
    if (root.dataset.dclBound) { return; }
    root.dataset.dclBound = "true";
    var form = root.querySelector(".dcl-order__form");
    var input = root.querySelector(".dcl-order__input");
    var message = root.querySelector(".dcl-order__message");
    if (!form || !input) { return; }
    function cents(node) { return node ? Number(node.dataset.cents) || 0 : 0; }
    function set(selector, text) { var node = root.querySelector(selector); if (node) { node.textContent = text; } }
    function render(percent, code) {
      var subtotal = 0;
      root.querySelectorAll(".dcl-order__line").forEach(function (cell) { subtotal += cents(cell); });
      var discount = Math.round(subtotal * percent / 100);
      var total = subtotal - discount + cents(root.querySelector(".dcl-order__shipping"));
      var row = root.querySelector(".dcl-order__discount-row");
      if (row) {
        row.hidden = discount === 0;
        var label = row.querySelector("th");
        if (label) { label.textContent = "Discount (" + code + ")"; }
      }
      set(".dcl-order__subtotal", money(subtotal));
      set(".dcl-order__discount", "-" + money(discount));
      set(".dcl-order__total", money(total));
      return total;
    }
    form.addEventListener("submit", function (event) {
      event.preventDefault();
      var code = input.value.trim().toUpperCase();
      var percent = codes[code];
      if (!percent) {
        input.setAttribute("aria-invalid", "true");
        if (message) {
          message.classList.add("dcl-order__message--error");
          message.textContent = code ? "The code " + code + " is not valid. Check it and try again." : "Enter a discount code.";
        }
        return;
      }
      input.removeAttribute("aria-invalid");
      var total = render(percent, code);
      if (message) {
        message.classList.remove("dcl-order__message--error");
        message.textContent = code + " applied: " + percent + "% off. New total " + money(total) + ".";
      }
    });
  });
})();
</script>

Was this useful? Send feedback.