Open demo

CSS code

HTML
<section class="mc" data-cart aria-label="Mini-cart demo">
<span class="lbl">Mini-cart dropdown</span>
<div class="mc-site">
  <div class="mc-bar">
    <span class="mc-logo">Kettle <b>&</b> Leaf</span>
    <nav class="mc-nav" aria-label="Shop"><a href="#">Teas</a><a href="#">Teaware</a><a href="#">Gifts</a></nav>
    <a class="mc-icon" href="#" aria-label="Account"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></a>
    <div class="mc-drop">
      <button class="mc-icon" type="button" data-dropdown aria-expanded="true" aria-controls="mc-panel"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="sr-only">Cart, </span><span class="mc-count" data-badge data-bump>3</span><span class="sr-only"> items</span></button>
      <div class="mc-panel" id="mc-panel">
        <div class="mc-panel-head"><h2 class="mc-panel-title">Your cart</h2><span data-count>3 items</span></div>
        <div data-filled>
          <ul class="mc-lines" data-lines>
            <li class="mc-line" data-line data-id="sencha" data-name="Sencha tin, 100 g" data-price="14.00" data-qty="2">
              <img class="mc-img" src="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=104&h=104&fit=crop&auto=format&q=70" alt="" width="52" height="52" data-line-img>
              <div><p class="mc-name" data-line-name>Sencha tin, 100 g</p><p class="mc-meta" data-line-qty>2 × $14.00</p></div>
              <p class="mc-total" data-line-total>$28.00</p>
              <button class="mc-x" type="button" data-remove aria-label="Remove Sencha tin, 100 g"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
            </li>
            <li class="mc-line" data-line data-id="cup" data-name="Stoneware tea cup" data-price="18.00" data-qty="1">
              <img class="mc-img" src="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=104&h=104&fit=crop&auto=format&q=70" alt="" width="52" height="52" data-line-img>
              <div><p class="mc-name" data-line-name>Stoneware tea cup</p><p class="mc-meta" data-line-qty>1 × $18.00</p></div>
              <p class="mc-total" data-line-total>$18.00</p>
              <button class="mc-x" type="button" data-remove aria-label="Remove Stoneware tea cup"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
            </li>
          </ul>
          <dl class="mc-sub"><dt>Subtotal</dt><dd data-subtotal>$46.00</dd></dl>
          <p class="mc-note">Shipping and taxes are worked out at checkout.</p>
          <div class="mc-actions"><a class="mc-btn mc-btn--ghost" href="#">View cart</a><a class="mc-btn" href="#">Checkout</a></div>
        </div>
        <p class="mc-empty" data-empty hidden tabindex="-1"><strong>Your cart is empty</strong>Add a tea below to see it here.</p>
        <template data-line-template><li class="mc-line" data-line><img class="mc-img" src="" alt="" width="52" height="52" data-line-img><div><p class="mc-name" data-line-name></p><p class="mc-meta" data-line-qty></p></div><p class="mc-total" data-line-total></p><button class="mc-x" type="button" data-remove aria-label="Remove"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></li></template>
      </div>
    </div>
  </div>
  <div class="mc-shop">
    <h3 class="mc-shop-title">Bestsellers</h3>
    <div class="mc-grid">
          <div class="mc-card"><img src="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3>Sencha tin, 100 g</h3><p>Green tea · $14.00</p>
            <button class="mc-btn" type="button" data-add="sencha" data-name="Sencha tin, 100 g" data-price="14.00" data-img="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="sr-only">: Sencha tin, 100 g</span></button></div>
          <div class="mc-card"><img src="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3>Stoneware tea cup</h3><p>Celadon glaze · $18.00</p>
            <button class="mc-btn" type="button" data-add="cup" data-name="Stoneware tea cup" data-price="18.00" data-img="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="sr-only">: Stoneware tea cup</span></button></div>
          <div class="mc-card"><img src="https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3>Butter shortbread tin</h3><p>12 biscuits · $9.50</p>
            <button class="mc-btn" type="button" data-add="shortbread" data-name="Butter shortbread tin" data-price="9.50" data-img="https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="sr-only">: Butter shortbread tin</span></button></div>
    </div>
  </div>
</div>
<span class="lbl mc-lbl2">Header badge styles</span>
<div class="mc-badges">
  <div class="mc-chip"><a class="mc-icon" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="sr-only">Cart, </span><span class="mc-count" data-badge data-bump>3</span><span class="sr-only"> items</span></a><small>Count bubble</small></div>
  <div class="mc-chip"><a class="mc-icon" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="mc-dot" aria-hidden="true"></span><span class="sr-only">Cart, <span data-badge>3</span> items</span></a><small>Dot</small></div>
  <div class="mc-chip"><a class="mc-pill" href="#"><span data-subtotal>$46.00</span><span class="mc-pill-n" data-bump><span class="sr-only">Cart, </span><span data-badge>3</span><span class="sr-only"> items</span></span></a><small>Total pill</small></div>
  <div class="mc-chip"><a class="mc-text" href="#">Cart (<span data-badge>3</span>)</a><small>Text</small></div>
  <div class="mc-chip"><a class="mc-bottom" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="mc-count" data-badge data-bump aria-hidden="true">3</span>Cart<span class="sr-only">, <span data-badge>3</span> items</span></a><small>Tab bar</small></div>
</div>
<p class="sr-only" role="status" data-announce></p>
</section>
CSS
/* Mini-cart dropdown and header cart badges. The cart button is a disclosure (aria-expanded + aria-controls);
   badge text is the real count, and a polite status region announces each change. */
.mc{--mc-accent:#3f6212;--mc-accent-dark:#365314;--mc-ink:#1c2419;--mc-muted:#566050;--mc-line:#e2e7dc;--mc-soft:#f2f5ee;--mc-badge:#b42318;
  --mc-ring:rgba(63,98,18,.32);color:var(--mc-ink);font-size:.93rem;line-height:1.45}
.mc [hidden]{display:none!important}
.mc .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}
.mc :where(button){font:inherit}
.mc :is(a,button):focus-visible,.mc-empty:focus-visible{outline:2px solid var(--mc-accent);outline-offset:2px}
.mc-empty:focus:not(:focus-visible){outline:none}
.mc-site{background:#fff;border-radius:18px;box-shadow:0 1px 2px rgba(28,36,25,.06),0 18px 40px -24px rgba(28,36,25,.35);min-height:540px}
.mc-bar{display:flex;align-items:center;gap:22px;padding:14px 20px;border-bottom:1px solid var(--mc-line)}
.mc-logo{font-family:Georgia,"Times New Roman",serif;font-size:1.25rem;margin-right:auto;white-space:nowrap}
.mc-logo b{color:var(--mc-accent);font-weight:400}
.mc-nav{display:flex;gap:20px}
.mc-nav a{color:var(--mc-muted);text-decoration:none;font-weight:500}
.mc-nav a:hover{color:var(--mc-ink)}
.mc-icon{position:relative;width:42px;height:42px;display:grid;place-items:center;border:0;border-radius:50%;background:none;color:var(--mc-ink);cursor:pointer;text-decoration:none}
.mc-icon:hover{background:var(--mc-soft)}
.mc-icon[aria-expanded="true"]{background:var(--mc-soft)}
.mc-drop{position:relative}
.mc-count{position:absolute;top:1px;right:0;min-width:19px;height:19px;padding:0 5px;border-radius:999px;background:var(--mc-badge);color:#fff;
  font-size:.7rem;font-weight:700;line-height:19px;text-align:center;box-shadow:0 0 0 2px #fff}
.is-bumped{animation:mc-bump .45s cubic-bezier(.3,1.6,.5,1)}
@keyframes mc-bump{0%{transform:scale(1)}40%{transform:scale(1.35)}100%{transform:scale(1)}}
/* panel */
.mc-panel{position:absolute;right:-6px;top:calc(100% + 10px);z-index:20;width:min(340px,calc(100vw - 48px));background:#fff;border:1px solid var(--mc-line);border-radius:16px;
  box-shadow:0 24px 50px -18px rgba(28,36,25,.4);padding:18px}
.mc-panel::before{content:"";position:absolute;top:-7px;right:20px;width:12px;height:12px;background:#fff;border:1px solid var(--mc-line);border-width:1px 0 0 1px;transform:rotate(45deg)}
.mc-panel-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px}
.mc-panel-title{font-size:1rem;font-weight:700}
.mc-panel-head span{color:var(--mc-muted);font-size:.85rem}
.mc-lines{list-style:none}
.mc-line{display:grid;grid-template-columns:52px minmax(0,1fr) auto 28px;gap:12px;align-items:center;padding:12px 0;border-bottom:1px solid var(--mc-line)}
.mc-img{width:52px;height:52px;border-radius:10px;object-fit:cover;background:var(--mc-soft)}
.mc-name{font-weight:600;font-size:.9rem;line-height:1.3}
.mc-meta{color:var(--mc-muted);font-size:.8rem}
.mc-total{font-weight:700;font-variant-numeric:tabular-nums;font-size:.9rem}
.mc-x{width:28px;height:28px;display:grid;place-items:center;border:0;border-radius:50%;background:none;color:var(--mc-muted);cursor:pointer}
.mc-x:hover{background:var(--mc-soft);color:var(--mc-ink)}
.mc-sub{display:flex;justify-content:space-between;padding:14px 0 4px;font-weight:700}
.mc-sub dd{font-variant-numeric:tabular-nums}
.mc-note{font-size:.78rem;color:var(--mc-muted);margin-bottom:14px}
.mc-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.mc-btn{display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 14px;border:1px solid var(--mc-accent);border-radius:10px;background:var(--mc-accent);color:#fff;
  font-weight:700;text-decoration:none;cursor:pointer;transition:background-color .15s,color .15s}
.mc-btn:hover{background:var(--mc-accent-dark)}
.mc-btn--ghost{background:#fff;color:var(--mc-accent)}
.mc-btn--ghost:hover{background:var(--mc-soft);color:var(--mc-accent-dark)}
.mc-empty{text-align:center;color:var(--mc-muted);padding:18px 6px 6px}
.mc-empty strong{display:block;color:var(--mc-ink);font-size:1rem;margin-bottom:2px}
/* products */
.mc-shop{padding:26px 20px 24px}
.mc-shop-title{font-family:Georgia,"Times New Roman",serif;font-weight:400;font-size:1.5rem;margin-bottom:16px}
.mc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:18px;max-width:640px}
.mc-card img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;border-radius:12px;background:var(--mc-soft)}
.mc-card h3{font-size:.92rem;font-weight:600;margin-top:10px;line-height:1.3}
.mc-card p{color:var(--mc-muted);font-size:.85rem;margin-bottom:10px}
.mc-card .mc-btn{width:100%;padding:9px 12px;font-size:.86rem}
/* badge styles */
.mc-badges{display:flex;flex-wrap:wrap;gap:14px}
.mc-chip{display:flex;align-items:center;gap:14px;padding:12px 16px;background:#fff;border-radius:14px;box-shadow:0 1px 2px rgba(28,36,25,.06),0 10px 24px -18px rgba(28,36,25,.4)}
.mc-chip small{font-size:.75rem;color:var(--mc-muted)}
.mc-dot{position:absolute;top:8px;right:8px;width:10px;height:10px;border-radius:50%;background:var(--mc-badge);box-shadow:0 0 0 2px #fff}
.mc-pill{display:inline-flex;align-items:center;gap:8px;padding:8px 8px 8px 12px;border-radius:999px;background:var(--mc-accent);color:#fff;text-decoration:none;font-weight:700;font-size:.86rem}
.mc-pill:hover{background:var(--mc-accent-dark)}
.mc-pill span.mc-pill-n{background:#fff;color:var(--mc-accent);border-radius:999px;min-width:24px;height:24px;padding:0 7px;display:inline-grid;place-items:center;font-size:.75rem}
.mc-text{color:var(--mc-ink);font-weight:600;text-decoration:underline;text-underline-offset:4px;text-decoration-color:var(--mc-line);text-decoration-thickness:2px}
.mc-text:hover{text-decoration-color:var(--mc-accent)}
.mc-bottom{display:inline-grid;justify-items:center;gap:2px;color:var(--mc-ink);text-decoration:none;font-size:.72rem;font-weight:600;position:relative;padding:4px 10px}
.mc-bottom .mc-count{top:-4px;right:2px}
@media (max-width:700px){.mc-nav{display:none}}
@media (prefers-reduced-motion:reduce){.is-bumped{animation:none}.mc-btn{transition:none}}
JavaScript
// Mini cart: [data-add] buttons put a product into the cart list or raise its quantity, [data-remove] buttons
// take it out, and every [data-badge], [data-count] and [data-subtotal] in the [data-cart] is refreshed. The
// polite [data-announce] region says what changed, so the new count is heard as well as seen.
document.querySelectorAll('[data-cart]').forEach(function (cart) {
  var money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
  var list = cart.querySelector('[data-lines]'), tpl = cart.querySelector('template[data-line-template]');
  var status = cart.querySelector('[data-announce]');
  function put(sel, text) { cart.querySelectorAll(sel).forEach(function (el) { el.textContent = text; }); }
  function render() {
    var sub = 0, count = 0;
    list.querySelectorAll('[data-line]').forEach(function (line) {
      var qty = +line.dataset.qty, price = Math.round(parseFloat(line.dataset.price) * 100);
      sub += price * qty; count += qty;
      line.querySelector('[data-line-qty]').textContent = qty + ' \u00d7 ' + money.format(price / 100);
      line.querySelector('[data-line-total]').textContent = money.format(price * qty / 100);
    });
    put('[data-badge]', String(count));
    put('[data-count]', count + (count === 1 ? ' item' : ' items'));
    put('[data-subtotal]', money.format(sub / 100));
    cart.querySelectorAll('[data-filled]').forEach(function (el) { el.hidden = !count; });
    cart.querySelectorAll('[data-empty]').forEach(function (el) { el.hidden = !!count; });
    return { sub: sub, count: count };
  }
  function bump() {
    cart.querySelectorAll('[data-bump]').forEach(function (el) { el.classList.remove('is-bumped'); void el.offsetWidth; el.classList.add('is-bumped'); });
  }
  function say(text) { if (status) status.textContent = text; }
  cart.addEventListener('click', function (e) {
    var add = e.target.closest('[data-add]'), rm = e.target.closest('[data-remove]'), r;
    if (add) {
      var line = list.querySelector('[data-id="' + add.dataset.add + '"]');
      if (line) line.dataset.qty = Math.min(10, +line.dataset.qty + 1);
      else {
        line = tpl.content.firstElementChild.cloneNode(true);
        line.dataset.id = add.dataset.add; line.dataset.name = add.dataset.name; line.dataset.price = add.dataset.price; line.dataset.qty = 1;
        line.querySelector('[data-line-name]').textContent = add.dataset.name;
        line.querySelector('[data-line-img]').src = add.dataset.img;
        line.querySelector('[data-remove]').setAttribute('aria-label', 'Remove ' + add.dataset.name);
        list.appendChild(line);
      }
      r = render(); bump();
      say('Added ' + add.dataset.name + '. ' + r.count + (r.count === 1 ? ' item' : ' items') + ' in your cart, subtotal ' + money.format(r.sub / 100) + '.');
    } else if (rm) {
      var gone = rm.closest('[data-line]'), next = gone.nextElementSibling || gone.previousElementSibling;
      gone.remove(); r = render(); bump();
      say('Removed ' + gone.dataset.name + '. ' + (r.count ? r.count + (r.count === 1 ? ' item' : ' items') + ' left, subtotal ' + money.format(r.sub / 100) + '.' : 'Your cart is empty.'));
      var target = next ? next.querySelector('[data-remove]') : cart.querySelector('[data-empty][tabindex="-1"]');
      if (target) target.focus();
    }
  });
  render();
});

// Dropdown: the cart button opens and closes its panel and keeps aria-expanded in step. Escape closes it and
// returns focus to the button; a click anywhere outside closes it.
document.querySelectorAll('[data-dropdown]').forEach(function (btn) {
  var panel = document.getElementById(btn.getAttribute('aria-controls'));
  function set(open) { btn.setAttribute('aria-expanded', open); panel.hidden = !open; }
  btn.addEventListener('click', function () { set(btn.getAttribute('aria-expanded') !== 'true'); });
  document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && !panel.hidden) { set(false); btn.focus(); }
  });
  document.addEventListener('click', function (e) {
    var path = e.composedPath();
    if (!panel.hidden && path.indexOf(panel) < 0 && path.indexOf(btn) < 0) set(false);
  });
});

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS, Bootstrap 5.3.8 JS bundle

HTML
<section class="mc" data-cart aria-label="Mini-cart demo">
<span class="lbl">Mini-cart dropdown</span>
<div class="card mc-site">
  <div class="mc-bar">
    <span class="mc-logo">Kettle <b>&</b> Leaf</span>
    <nav aria-label="Shop"><ul class="nav"><li class="nav-item"><a class="nav-link" href="#">Teas</a></li><li class="nav-item"><a class="nav-link" href="#">Teaware</a></li><li class="nav-item"><a class="nav-link" href="#">Gifts</a></li></ul></nav>
    <a class="btn mc-icon" href="#" aria-label="Account"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></a>
    <div class="dropdown">
      <button class="btn mc-icon show" type="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" data-bs-display="static" aria-expanded="true" aria-controls="mc-panel"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="visually-hidden">Cart, </span><span class="badge rounded-pill" data-badge data-bump>3</span><span class="visually-hidden"> items</span></button>
      <div class="dropdown-menu show" id="mc-panel">
        <div class="mc-panel-head"><h2 class="mc-panel-title">Your cart</h2><span data-count>3 items</span></div>
        <div data-filled>
          <ul class="list-group" data-lines>
            <li class="list-group-item mc-line" data-line data-id="sencha" data-name="Sencha tin, 100 g" data-price="14.00" data-qty="2">
              <img class="mc-img" src="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=104&h=104&fit=crop&auto=format&q=70" alt="" width="52" height="52" data-line-img>
              <div><p class="mc-name" data-line-name>Sencha tin, 100 g</p><p class="mc-meta" data-line-qty>2 × $14.00</p></div>
              <p class="mc-total" data-line-total>$28.00</p>
              <button class="btn mc-x" type="button" data-remove aria-label="Remove Sencha tin, 100 g"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
            </li>
            <li class="list-group-item mc-line" data-line data-id="cup" data-name="Stoneware tea cup" data-price="18.00" data-qty="1">
              <img class="mc-img" src="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=104&h=104&fit=crop&auto=format&q=70" alt="" width="52" height="52" data-line-img>
              <div><p class="mc-name" data-line-name>Stoneware tea cup</p><p class="mc-meta" data-line-qty>1 × $18.00</p></div>
              <p class="mc-total" data-line-total>$18.00</p>
              <button class="btn mc-x" type="button" data-remove aria-label="Remove Stoneware tea cup"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
            </li>
          </ul>
          <dl class="mc-sub"><dt>Subtotal</dt><dd data-subtotal>$46.00</dd></dl>
          <p class="mc-note">Shipping and taxes are worked out at checkout.</p>
          <div class="mc-actions"><a class="btn btn-outline-primary" href="#">View cart</a><a class="btn btn-primary" href="#">Checkout</a></div>
        </div>
        <p class="mc-empty" data-empty hidden tabindex="-1"><strong>Your cart is empty</strong>Add a tea below to see it here.</p>
        <template data-line-template><li class="list-group-item mc-line" data-line><img class="mc-img" src="" alt="" width="52" height="52" data-line-img><div><p class="mc-name" data-line-name></p><p class="mc-meta" data-line-qty></p></div><p class="mc-total" data-line-total></p><button class="btn mc-x" type="button" data-remove aria-label="Remove"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></li></template>
      </div>
    </div>
  </div>
  <div class="mc-shop">
    <h3 class="mc-shop-title">Bestsellers</h3>
    <div class="mc-grid">
          <div class="mc-card"><img src="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3>Sencha tin, 100 g</h3><p>Green tea · $14.00</p>
            <button class="btn btn-primary" type="button" data-add="sencha" data-name="Sencha tin, 100 g" data-price="14.00" data-img="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="visually-hidden">: Sencha tin, 100 g</span></button></div>
          <div class="mc-card"><img src="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3>Stoneware tea cup</h3><p>Celadon glaze · $18.00</p>
            <button class="btn btn-primary" type="button" data-add="cup" data-name="Stoneware tea cup" data-price="18.00" data-img="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="visually-hidden">: Stoneware tea cup</span></button></div>
          <div class="mc-card"><img src="https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3>Butter shortbread tin</h3><p>12 biscuits · $9.50</p>
            <button class="btn btn-primary" type="button" data-add="shortbread" data-name="Butter shortbread tin" data-price="9.50" data-img="https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="visually-hidden">: Butter shortbread tin</span></button></div>
    </div>
  </div>
</div>
<span class="lbl mc-lbl2">Header badge styles</span>
<div class="mc-badges">
  <div class="mc-chip"><a class="btn mc-icon" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="visually-hidden">Cart, </span><span class="badge rounded-pill" data-badge data-bump>3</span><span class="visually-hidden"> items</span></a><small>Count bubble</small></div>
  <div class="mc-chip"><a class="btn mc-icon" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="mc-dot" aria-hidden="true"></span><span class="visually-hidden">Cart, <span data-badge>3</span> items</span></a><small>Dot</small></div>
  <div class="mc-chip"><a class="btn btn-primary mc-pill" href="#"><span data-subtotal>$46.00</span><span class="badge rounded-pill" data-bump><span class="visually-hidden">Cart, </span><span data-badge>3</span><span class="visually-hidden"> items</span></span></a><small>Total pill</small></div>
  <div class="mc-chip"><a class="mc-text" href="#">Cart (<span data-badge>3</span>)</a><small>Text</small></div>
  <div class="mc-chip"><a class="mc-bottom" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="badge rounded-pill" data-badge data-bump aria-hidden="true">3</span>Cart<span class="visually-hidden">, <span data-badge>3</span> items</span></a><small>Tab bar</small></div>
</div>
<p class="visually-hidden" role="status" data-announce></p>
</section>
CSS
/* Bootstrap .dropdown (data-bs-auto-close="outside", so remove buttons don't close it), .badge, .btn and .card,
   recoloured through Bootstrap's CSS variables. Badge text is the real count; a polite status region announces changes. */
.mc{--mc-accent:#3f6212;--mc-accent-dark:#365314;--mc-ink:#1c2419;--mc-muted:#566050;--mc-line:#e2e7dc;--mc-soft:#f2f5ee;--mc-badge:#b42318;
  --bs-body-color:var(--mc-ink);--bs-secondary-color:var(--mc-muted);--bs-border-color:var(--mc-line);color:var(--mc-ink);font-size:.93rem;line-height:1.45}
.mc p,.mc h2,.mc h3,.mc dl,.mc dd,.mc ul{margin:0}
.mc .btn:focus-visible,.mc a:focus-visible,.mc-empty:focus-visible{outline:2px solid var(--mc-accent);outline-offset:2px;box-shadow:none}
.mc-empty:focus:not(:focus-visible){outline:none}
.mc-site{--bs-card-border-width:0;--bs-card-border-radius:18px;box-shadow:0 1px 2px rgba(28,36,25,.06),0 18px 40px -24px rgba(28,36,25,.35);min-height:540px}
.mc-bar{display:flex;align-items:center;gap:22px;padding:14px 20px;border-bottom:1px solid var(--mc-line)}
.mc-logo{font-family:Georgia,"Times New Roman",serif;font-size:1.25rem;margin-right:auto;white-space:nowrap}
.mc-logo b{color:var(--mc-accent);font-weight:400}
.mc .nav{--bs-nav-link-color:var(--mc-muted);--bs-nav-link-hover-color:var(--mc-ink);--bs-nav-link-padding-x:0;--bs-nav-link-padding-y:0;--bs-nav-link-font-weight:500;gap:20px}
.mc-icon{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;--bs-btn-color:var(--mc-ink);--bs-btn-hover-color:var(--mc-ink);--bs-btn-hover-bg:var(--mc-soft);
  --bs-btn-active-bg:var(--mc-soft);--bs-btn-active-color:var(--mc-ink);position:relative;width:42px;height:42px;display:grid;place-items:center;border-radius:50%}
.mc-icon.show{background:var(--mc-soft)}
.mc .badge{--bs-badge-font-size:.7rem;--bs-badge-padding-x:5px;--bs-badge-padding-y:0;--bs-badge-font-weight:700;min-width:19px;height:19px;line-height:19px;background:var(--mc-badge);box-shadow:0 0 0 2px #fff}
.mc-icon .badge{position:absolute;top:1px;right:0}
.is-bumped{animation:mc-bump .45s cubic-bezier(.3,1.6,.5,1)}
@keyframes mc-bump{0%{transform:scale(1)}40%{transform:scale(1.35)}100%{transform:scale(1)}}
.mc .dropdown-menu{--bs-dropdown-min-width:0;--bs-dropdown-padding-x:18px;--bs-dropdown-padding-y:18px;--bs-dropdown-border-color:var(--mc-line);--bs-dropdown-border-radius:16px;
  --bs-dropdown-color:var(--mc-ink);--bs-dropdown-font-size:.93rem;width:min(340px,calc(100vw - 48px));box-shadow:0 24px 50px -18px rgba(28,36,25,.4);
  position:absolute;right:-6px;left:auto;top:calc(100% + 10px);margin:0}
.mc .dropdown-menu::before{content:"";position:absolute;top:-7px;right:20px;width:12px;height:12px;background:#fff;border:1px solid var(--mc-line);border-width:1px 0 0 1px;transform:rotate(45deg)}
.mc-panel-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:6px}
.mc-panel-title{font-size:1rem;font-weight:700}
.mc-panel-head span{color:var(--mc-muted);font-size:.85rem}
.mc .list-group{--bs-list-group-border-width:0;--bs-list-group-item-padding-x:0;--bs-list-group-item-padding-y:12px;--bs-list-group-bg:transparent;--bs-list-group-color:var(--mc-ink)}
.mc-line{display:grid!important;grid-template-columns:52px minmax(0,1fr) auto 28px;gap:12px;align-items:center;border-bottom:1px solid var(--mc-line)!important}
.mc-img{width:52px;height:52px;border-radius:10px;object-fit:cover;background:var(--mc-soft)}
.mc-name{font-weight:600;font-size:.9rem;line-height:1.3}
.mc-meta{color:var(--mc-muted);font-size:.8rem}
.mc-total{font-weight:700;font-variant-numeric:tabular-nums;font-size:.9rem}
.mc-x{--bs-btn-padding-x:0;--bs-btn-padding-y:0;--bs-btn-border-width:0;--bs-btn-color:var(--mc-muted);--bs-btn-hover-color:var(--mc-ink);--bs-btn-hover-bg:var(--mc-soft);
  width:28px;height:28px;display:grid;place-items:center;border-radius:50%}
.mc-sub{display:flex;justify-content:space-between;padding:14px 0 4px;font-weight:700}
.mc-sub dt{font-weight:700}
.mc-sub dd{font-variant-numeric:tabular-nums}
.mc-note{font-size:.78rem;color:var(--mc-muted);margin-bottom:14px!important}
.mc-actions{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.mc .btn-primary{--bs-btn-bg:var(--mc-accent);--bs-btn-border-color:var(--mc-accent);--bs-btn-hover-bg:var(--mc-accent-dark);--bs-btn-hover-border-color:var(--mc-accent-dark);
  --bs-btn-active-bg:var(--mc-accent-dark);--bs-btn-active-border-color:var(--mc-accent-dark);--bs-btn-font-weight:700;--bs-btn-padding-y:10px;--bs-btn-border-radius:10px}
.mc .btn-outline-primary{--bs-btn-color:var(--mc-accent);--bs-btn-border-color:var(--mc-accent);--bs-btn-hover-bg:var(--mc-soft);--bs-btn-hover-color:var(--mc-accent-dark);
  --bs-btn-hover-border-color:var(--mc-accent);--bs-btn-active-bg:var(--mc-line);--bs-btn-active-color:var(--mc-accent-dark);--bs-btn-active-border-color:var(--mc-accent);
  --bs-btn-font-weight:700;--bs-btn-padding-y:10px;--bs-btn-border-radius:10px}
.mc-empty{text-align:center;color:var(--mc-muted);padding:18px 6px 6px}
.mc-empty strong{display:block;color:var(--mc-ink);font-size:1rem;margin-bottom:2px}
.mc-shop{padding:26px 20px 24px}
.mc-shop-title{font-family:Georgia,"Times New Roman",serif;font-weight:400;font-size:1.5rem;margin-bottom:16px!important}
.mc-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:18px;max-width:640px}
.mc-card img{width:100%;height:auto;aspect-ratio:1;object-fit:cover;border-radius:12px;background:var(--mc-soft)}
.mc-card h3{font-size:.92rem;font-weight:600;margin-top:10px;line-height:1.3}
.mc-card p{color:var(--mc-muted);font-size:.85rem;margin-bottom:10px}
.mc-card .btn{--bs-btn-padding-y:8px;--bs-btn-font-size:.86rem;width:100%}
.mc-badges{display:flex;flex-wrap:wrap;gap:14px}
.mc-chip{display:flex;align-items:center;gap:14px;padding:12px 16px;background:#fff;border-radius:14px;box-shadow:0 1px 2px rgba(28,36,25,.06),0 10px 24px -18px rgba(28,36,25,.4)}
.mc-chip small{font-size:.75rem;color:var(--mc-muted)}
.mc-dot{position:absolute;top:8px;right:8px;width:10px;height:10px;border-radius:50%;background:var(--mc-badge);box-shadow:0 0 0 2px #fff}
.mc-pill{--bs-btn-padding-x:12px;--bs-btn-padding-y:8px;--bs-btn-font-size:.86rem;padding-right:8px;border-radius:999px;display:inline-flex;align-items:center;gap:8px}
.mc-pill .badge{--bs-badge-font-size:.75rem;background:#fff;color:var(--mc-accent);box-shadow:none;min-width:24px;height:24px;padding:0 7px;display:inline-grid;place-items:center;line-height:1}
.mc-text{color:var(--mc-ink);font-weight:600;text-underline-offset:4px;text-decoration-color:var(--mc-line);text-decoration-thickness:2px}
.mc-text:hover{color:var(--mc-ink);text-decoration-color:var(--mc-accent)}
.mc-bottom{display:inline-grid;justify-items:center;gap:2px;color:var(--mc-ink);text-decoration:none;font-size:.72rem;font-weight:600;position:relative;padding:4px 10px}
.mc-bottom .badge{position:absolute;top:-4px;right:2px}
@media (max-width:700px){.mc .nav{display:none}}
@media (prefers-reduced-motion:reduce){.is-bumped{animation:none}.mc .btn{transition:none}}
JavaScript
// Mini cart: [data-add] buttons put a product into the cart list or raise its quantity, [data-remove] buttons
// take it out, and every [data-badge], [data-count] and [data-subtotal] in the [data-cart] is refreshed. The
// polite [data-announce] region says what changed, so the new count is heard as well as seen.
document.querySelectorAll('[data-cart]').forEach(function (cart) {
  var money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
  var list = cart.querySelector('[data-lines]'), tpl = cart.querySelector('template[data-line-template]');
  var status = cart.querySelector('[data-announce]');
  function put(sel, text) { cart.querySelectorAll(sel).forEach(function (el) { el.textContent = text; }); }
  function render() {
    var sub = 0, count = 0;
    list.querySelectorAll('[data-line]').forEach(function (line) {
      var qty = +line.dataset.qty, price = Math.round(parseFloat(line.dataset.price) * 100);
      sub += price * qty; count += qty;
      line.querySelector('[data-line-qty]').textContent = qty + ' \u00d7 ' + money.format(price / 100);
      line.querySelector('[data-line-total]').textContent = money.format(price * qty / 100);
    });
    put('[data-badge]', String(count));
    put('[data-count]', count + (count === 1 ? ' item' : ' items'));
    put('[data-subtotal]', money.format(sub / 100));
    cart.querySelectorAll('[data-filled]').forEach(function (el) { el.hidden = !count; });
    cart.querySelectorAll('[data-empty]').forEach(function (el) { el.hidden = !!count; });
    return { sub: sub, count: count };
  }
  function bump() {
    cart.querySelectorAll('[data-bump]').forEach(function (el) { el.classList.remove('is-bumped'); void el.offsetWidth; el.classList.add('is-bumped'); });
  }
  function say(text) { if (status) status.textContent = text; }
  cart.addEventListener('click', function (e) {
    var add = e.target.closest('[data-add]'), rm = e.target.closest('[data-remove]'), r;
    if (add) {
      var line = list.querySelector('[data-id="' + add.dataset.add + '"]');
      if (line) line.dataset.qty = Math.min(10, +line.dataset.qty + 1);
      else {
        line = tpl.content.firstElementChild.cloneNode(true);
        line.dataset.id = add.dataset.add; line.dataset.name = add.dataset.name; line.dataset.price = add.dataset.price; line.dataset.qty = 1;
        line.querySelector('[data-line-name]').textContent = add.dataset.name;
        line.querySelector('[data-line-img]').src = add.dataset.img;
        line.querySelector('[data-remove]').setAttribute('aria-label', 'Remove ' + add.dataset.name);
        list.appendChild(line);
      }
      r = render(); bump();
      say('Added ' + add.dataset.name + '. ' + r.count + (r.count === 1 ? ' item' : ' items') + ' in your cart, subtotal ' + money.format(r.sub / 100) + '.');
    } else if (rm) {
      var gone = rm.closest('[data-line]'), next = gone.nextElementSibling || gone.previousElementSibling;
      gone.remove(); r = render(); bump();
      say('Removed ' + gone.dataset.name + '. ' + (r.count ? r.count + (r.count === 1 ? ' item' : ' items') + ' left, subtotal ' + money.format(r.sub / 100) + '.' : 'Your cart is empty.'));
      var target = next ? next.querySelector('[data-remove]') : cart.querySelector('[data-empty][tabindex="-1"]');
      if (target) target.focus();
    }
  });
  render();
});

// Bootstrap's dropdown plugin does the opening and closing; creating the instances up front lets a dropdown
// that starts open (.show in the markup) close on an outside click too.
document.querySelectorAll('[data-bs-toggle="dropdown"]').forEach(function (btn) { bootstrap.Dropdown.getOrCreateInstance(btn); });

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<section class="text-[.93rem] leading-[1.45] text-mc-ink" data-cart aria-label="Mini-cart demo">
<span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#4f5949] uppercase">Mini-cart dropdown</span>
<div class="min-h-[540px] rounded-[18px] bg-white shadow-[0_1px_2px_rgba(28,36,25,.06),0_18px_40px_-24px_rgba(28,36,25,.35)]">
  <div class="flex items-center gap-[22px] border-b border-mc-line px-5 py-3.5">
    <span class="mr-auto font-[Georgia,'Times_New_Roman',serif] text-[1.25rem] whitespace-nowrap">Kettle <b class="font-normal text-mc-accent">&</b> Leaf</span>
    <nav class="flex gap-5 max-[700px]:hidden" aria-label="Shop"><a class="font-medium text-mc-muted hover:text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#">Teas</a><a class="font-medium text-mc-muted hover:text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#">Teaware</a><a class="font-medium text-mc-muted hover:text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#">Gifts</a></nav>
    <a class="relative grid size-[42px] cursor-pointer place-items-center rounded-full text-mc-ink hover:bg-mc-soft aria-expanded:bg-mc-soft focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#" aria-label="Account"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg></a>
    <div class="relative">
      <button class="relative grid size-[42px] cursor-pointer place-items-center rounded-full text-mc-ink hover:bg-mc-soft aria-expanded:bg-mc-soft focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-dropdown aria-expanded="true" aria-controls="mc-panel"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="sr-only">Cart, </span><span class="absolute top-px right-0 h-[19px] min-w-[19px] rounded-full bg-mc-badge px-[5px] text-center text-[.7rem] leading-[19px] font-bold text-white shadow-[0_0_0_2px_#fff] [&.is-bumped]:animate-[mc-bump_.45s_cubic-bezier(.3,1.6,.5,1)] motion-reduce:[&.is-bumped]:animate-none" data-badge data-bump>3</span><span class="sr-only"> items</span></button>
      <div class="absolute top-[calc(100%+10px)] -right-1.5 z-20 w-[min(340px,calc(100vw-48px))] rounded-2xl border border-mc-line bg-white p-[18px] shadow-[0_24px_50px_-18px_rgba(28,36,25,.4)] before:absolute before:-top-[7px] before:right-5 before:size-3 before:rotate-45 before:border-t before:border-l before:border-mc-line before:bg-white before:content-['']" id="mc-panel">
        <div class="mb-1.5 flex items-baseline justify-between"><h2 class="text-base font-bold">Your cart</h2><span class="text-[.85rem] text-mc-muted" data-count>3 items</span></div>
        <div data-filled>
          <ul data-lines>
            <li class="grid grid-cols-[52px_minmax(0,1fr)_auto_28px] items-center gap-3 border-b border-mc-line py-3" data-line data-id="sencha" data-name="Sencha tin, 100 g" data-price="14.00" data-qty="2">
              <img class="size-[52px] rounded-[10px] bg-mc-soft object-cover" src="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=104&h=104&fit=crop&auto=format&q=70" alt="" width="52" height="52" data-line-img>
              <div><p class="text-[.9rem] leading-[1.3] font-semibold" data-line-name>Sencha tin, 100 g</p><p class="text-[.8rem] text-mc-muted" data-line-qty>2 × $14.00</p></div>
              <p class="text-[.9rem] font-bold tabular-nums" data-line-total>$28.00</p>
              <button class="grid size-7 cursor-pointer place-items-center rounded-full text-mc-muted hover:bg-mc-soft hover:text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-remove aria-label="Remove Sencha tin, 100 g"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
            </li>
            <li class="grid grid-cols-[52px_minmax(0,1fr)_auto_28px] items-center gap-3 border-b border-mc-line py-3" data-line data-id="cup" data-name="Stoneware tea cup" data-price="18.00" data-qty="1">
              <img class="size-[52px] rounded-[10px] bg-mc-soft object-cover" src="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=104&h=104&fit=crop&auto=format&q=70" alt="" width="52" height="52" data-line-img>
              <div><p class="text-[.9rem] leading-[1.3] font-semibold" data-line-name>Stoneware tea cup</p><p class="text-[.8rem] text-mc-muted" data-line-qty>1 × $18.00</p></div>
              <p class="text-[.9rem] font-bold tabular-nums" data-line-total>$18.00</p>
              <button class="grid size-7 cursor-pointer place-items-center rounded-full text-mc-muted hover:bg-mc-soft hover:text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-remove aria-label="Remove Stoneware tea cup"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
            </li>
          </ul>
          <dl class="flex justify-between pt-3.5 pb-1 font-bold"><dt>Subtotal</dt><dd class="tabular-nums" data-subtotal>$46.00</dd></dl>
          <p class="mb-3.5 text-[.78rem] text-mc-muted">Shipping and taxes are worked out at checkout.</p>
          <div class="grid grid-cols-2 gap-2.5"><a class="flex cursor-pointer items-center justify-center gap-1.5 rounded-[10px] border border-mc-accent bg-white px-3.5 py-[11px] font-bold text-mc-accent no-underline transition-colors duration-150 hover:bg-mc-soft hover:text-mc-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#">View cart</a><a class="flex cursor-pointer items-center justify-center gap-1.5 rounded-[10px] border border-mc-accent bg-mc-accent px-3.5 py-[11px] font-bold text-white no-underline transition-colors duration-150 hover:bg-mc-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#">Checkout</a></div>
        </div>
        <p class="px-1.5 pt-[18px] pb-1.5 text-center text-mc-muted focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" data-empty hidden tabindex="-1"><strong class="mb-0.5 block text-base text-mc-ink">Your cart is empty</strong>Add a tea below to see it here.</p>
        <template data-line-template><li class="grid grid-cols-[52px_minmax(0,1fr)_auto_28px] items-center gap-3 border-b border-mc-line py-3" data-line><img class="size-[52px] rounded-[10px] bg-mc-soft object-cover" src="" alt="" width="52" height="52" data-line-img><div><p class="text-[.9rem] leading-[1.3] font-semibold" data-line-name></p><p class="text-[.8rem] text-mc-muted" data-line-qty></p></div><p class="text-[.9rem] font-bold tabular-nums" data-line-total></p><button class="grid size-7 cursor-pointer place-items-center rounded-full text-mc-muted hover:bg-mc-soft hover:text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-remove aria-label="Remove"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button></li></template>
      </div>
    </div>
  </div>
  <div class="px-5 pt-[26px] pb-6">
    <h3 class="mb-4 font-[Georgia,'Times_New_Roman',serif] text-[1.5rem] font-normal">Bestsellers</h3>
    <div class="grid max-w-[640px] grid-cols-[repeat(auto-fill,minmax(150px,1fr))] gap-[18px]">
          <div><img class="aspect-square w-full rounded-xl bg-mc-soft object-cover" src="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3 class="mt-2.5 text-[.92rem] leading-[1.3] font-semibold">Sencha tin, 100 g</h3><p class="mb-2.5 text-[.85rem] text-mc-muted">Green tea · $14.00</p>
            <button class="w-full !py-[9px] text-[.86rem] flex cursor-pointer items-center justify-center gap-1.5 rounded-[10px] border border-mc-accent bg-mc-accent px-3.5 py-[11px] font-bold text-white no-underline transition-colors duration-150 hover:bg-mc-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-add="sencha" data-name="Sencha tin, 100 g" data-price="14.00" data-img="https://images.unsplash.com/photo-1563822249548-9a72b6353cd1?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="sr-only">: Sencha tin, 100 g</span></button></div>
          <div><img class="aspect-square w-full rounded-xl bg-mc-soft object-cover" src="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3 class="mt-2.5 text-[.92rem] leading-[1.3] font-semibold">Stoneware tea cup</h3><p class="mb-2.5 text-[.85rem] text-mc-muted">Celadon glaze · $18.00</p>
            <button class="w-full !py-[9px] text-[.86rem] flex cursor-pointer items-center justify-center gap-1.5 rounded-[10px] border border-mc-accent bg-mc-accent px-3.5 py-[11px] font-bold text-white no-underline transition-colors duration-150 hover:bg-mc-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-add="cup" data-name="Stoneware tea cup" data-price="18.00" data-img="https://images.unsplash.com/photo-1571934811356-5cc061b6821f?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="sr-only">: Stoneware tea cup</span></button></div>
          <div><img class="aspect-square w-full rounded-xl bg-mc-soft object-cover" src="https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=320&h=320&fit=crop&auto=format&q=70" alt="" width="160" height="160"><h3 class="mt-2.5 text-[.92rem] leading-[1.3] font-semibold">Butter shortbread tin</h3><p class="mb-2.5 text-[.85rem] text-mc-muted">12 biscuits · $9.50</p>
            <button class="w-full !py-[9px] text-[.86rem] flex cursor-pointer items-center justify-center gap-1.5 rounded-[10px] border border-mc-accent bg-mc-accent px-3.5 py-[11px] font-bold text-white no-underline transition-colors duration-150 hover:bg-mc-accent-dark motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" type="button" data-add="shortbread" data-name="Butter shortbread tin" data-price="9.50" data-img="https://images.unsplash.com/photo-1544787219-7f47ccb76574?w=104&h=104&fit=crop&auto=format&q=70">Add to cart<span class="sr-only">: Butter shortbread tin</span></button></div>
    </div>
  </div>
</div>
<span class="mt-[50px] mb-4 block text-[.72rem] tracking-[.12em] text-[#4f5949] uppercase">Header badge styles</span>
<div class="flex flex-wrap gap-3.5">
  <div class="flex items-center gap-3.5 rounded-[14px] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(28,36,25,.06),0_10px_24px_-18px_rgba(28,36,25,.4)]"><a class="relative grid size-[42px] cursor-pointer place-items-center rounded-full text-mc-ink hover:bg-mc-soft aria-expanded:bg-mc-soft focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="sr-only">Cart, </span><span class="absolute top-px right-0 h-[19px] min-w-[19px] rounded-full bg-mc-badge px-[5px] text-center text-[.7rem] leading-[19px] font-bold text-white shadow-[0_0_0_2px_#fff] [&.is-bumped]:animate-[mc-bump_.45s_cubic-bezier(.3,1.6,.5,1)] motion-reduce:[&.is-bumped]:animate-none" data-badge data-bump>3</span><span class="sr-only"> items</span></a><small class="text-[.75rem] text-mc-muted">Count bubble</small></div>
  <div class="flex items-center gap-3.5 rounded-[14px] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(28,36,25,.06),0_10px_24px_-18px_rgba(28,36,25,.4)]"><a class="relative grid size-[42px] cursor-pointer place-items-center rounded-full text-mc-ink hover:bg-mc-soft aria-expanded:bg-mc-soft focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="absolute top-2 right-2 size-2.5 rounded-full bg-mc-badge shadow-[0_0_0_2px_#fff]" aria-hidden="true"></span><span class="sr-only">Cart, <span data-badge>3</span> items</span></a><small class="text-[.75rem] text-mc-muted">Dot</small></div>
  <div class="flex items-center gap-3.5 rounded-[14px] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(28,36,25,.06),0_10px_24px_-18px_rgba(28,36,25,.4)]"><a class="inline-flex items-center gap-2 rounded-full bg-mc-accent py-2 pr-2 pl-3 text-[.86rem] font-bold text-white hover:bg-mc-accent-dark focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#"><span data-subtotal>$46.00</span><span class="inline-grid h-6 min-w-6 place-items-center rounded-full bg-white px-[7px] text-[.75rem] text-mc-accent [&.is-bumped]:animate-[mc-bump_.45s_cubic-bezier(.3,1.6,.5,1)] motion-reduce:[&.is-bumped]:animate-none" data-bump><span class="sr-only">Cart, </span><span data-badge>3</span><span class="sr-only"> items</span></span></a><small class="text-[.75rem] text-mc-muted">Total pill</small></div>
  <div class="flex items-center gap-3.5 rounded-[14px] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(28,36,25,.06),0_10px_24px_-18px_rgba(28,36,25,.4)]"><a class="font-semibold text-mc-ink underline decoration-mc-line decoration-2 underline-offset-4 hover:decoration-mc-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#">Cart (<span data-badge>3</span>)</a><small class="text-[.75rem] text-mc-muted">Text</small></div>
  <div class="flex items-center gap-3.5 rounded-[14px] bg-white px-4 py-3 shadow-[0_1px_2px_rgba(28,36,25,.06),0_10px_24px_-18px_rgba(28,36,25,.4)]"><a class="relative inline-grid justify-items-center gap-0.5 px-2.5 py-1 text-[.72rem] font-semibold text-mc-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-mc-accent" href="#"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4h2.2l2.1 10.2a1.8 1.8 0 0 0 1.8 1.4h7.8a1.8 1.8 0 0 0 1.7-1.3L20.5 8H6.2"/><circle cx="9.5" cy="19.5" r="1.3"/><circle cx="17" cy="19.5" r="1.3"/></svg><span class="absolute top-px right-0 h-[19px] min-w-[19px] rounded-full bg-mc-badge px-[5px] text-center text-[.7rem] leading-[19px] font-bold text-white shadow-[0_0_0_2px_#fff] [&.is-bumped]:animate-[mc-bump_.45s_cubic-bezier(.3,1.6,.5,1)] motion-reduce:[&.is-bumped]:animate-none !-top-1 !right-0.5" data-badge data-bump aria-hidden="true">3</span>Cart<span class="sr-only">, <span data-badge>3</span> items</span></a><small class="text-[.75rem] text-mc-muted">Tab bar</small></div>
</div>
<p class="sr-only" role="status" data-announce></p>
</section>
CSS
@theme {
  --color-mc-accent: #3f6212;
  --color-mc-accent-dark: #365314;
  --color-mc-ink: #1c2419;
  --color-mc-muted: #566050;
  --color-mc-line: #e2e7dc;
  --color-mc-soft: #f2f5ee;
  --color-mc-badge: #b42318;
}
/* the badge pops when the count changes */
@keyframes mc-bump { 0% { transform: scale(1) } 40% { transform: scale(1.35) } 100% { transform: scale(1) } }
JavaScript
// Mini cart: [data-add] buttons put a product into the cart list or raise its quantity, [data-remove] buttons
// take it out, and every [data-badge], [data-count] and [data-subtotal] in the [data-cart] is refreshed. The
// polite [data-announce] region says what changed, so the new count is heard as well as seen.
document.querySelectorAll('[data-cart]').forEach(function (cart) {
  var money = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
  var list = cart.querySelector('[data-lines]'), tpl = cart.querySelector('template[data-line-template]');
  var status = cart.querySelector('[data-announce]');
  function put(sel, text) { cart.querySelectorAll(sel).forEach(function (el) { el.textContent = text; }); }
  function render() {
    var sub = 0, count = 0;
    list.querySelectorAll('[data-line]').forEach(function (line) {
      var qty = +line.dataset.qty, price = Math.round(parseFloat(line.dataset.price) * 100);
      sub += price * qty; count += qty;
      line.querySelector('[data-line-qty]').textContent = qty + ' \u00d7 ' + money.format(price / 100);
      line.querySelector('[data-line-total]').textContent = money.format(price * qty / 100);
    });
    put('[data-badge]', String(count));
    put('[data-count]', count + (count === 1 ? ' item' : ' items'));
    put('[data-subtotal]', money.format(sub / 100));
    cart.querySelectorAll('[data-filled]').forEach(function (el) { el.hidden = !count; });
    cart.querySelectorAll('[data-empty]').forEach(function (el) { el.hidden = !!count; });
    return { sub: sub, count: count };
  }
  function bump() {
    cart.querySelectorAll('[data-bump]').forEach(function (el) { el.classList.remove('is-bumped'); void el.offsetWidth; el.classList.add('is-bumped'); });
  }
  function say(text) { if (status) status.textContent = text; }
  cart.addEventListener('click', function (e) {
    var add = e.target.closest('[data-add]'), rm = e.target.closest('[data-remove]'), r;
    if (add) {
      var line = list.querySelector('[data-id="' + add.dataset.add + '"]');
      if (line) line.dataset.qty = Math.min(10, +line.dataset.qty + 1);
      else {
        line = tpl.content.firstElementChild.cloneNode(true);
        line.dataset.id = add.dataset.add; line.dataset.name = add.dataset.name; line.dataset.price = add.dataset.price; line.dataset.qty = 1;
        line.querySelector('[data-line-name]').textContent = add.dataset.name;
        line.querySelector('[data-line-img]').src = add.dataset.img;
        line.querySelector('[data-remove]').setAttribute('aria-label', 'Remove ' + add.dataset.name);
        list.appendChild(line);
      }
      r = render(); bump();
      say('Added ' + add.dataset.name + '. ' + r.count + (r.count === 1 ? ' item' : ' items') + ' in your cart, subtotal ' + money.format(r.sub / 100) + '.');
    } else if (rm) {
      var gone = rm.closest('[data-line]'), next = gone.nextElementSibling || gone.previousElementSibling;
      gone.remove(); r = render(); bump();
      say('Removed ' + gone.dataset.name + '. ' + (r.count ? r.count + (r.count === 1 ? ' item' : ' items') + ' left, subtotal ' + money.format(r.sub / 100) + '.' : 'Your cart is empty.'));
      var target = next ? next.querySelector('[data-remove]') : cart.querySelector('[data-empty][tabindex="-1"]');
      if (target) target.focus();
    }
  });
  render();
});

// Dropdown: the cart button opens and closes its panel and keeps aria-expanded in step. Escape closes it and
// returns focus to the button; a click anywhere outside closes it.
document.querySelectorAll('[data-dropdown]').forEach(function (btn) {
  var panel = document.getElementById(btn.getAttribute('aria-controls'));
  function set(open) { btn.setAttribute('aria-expanded', open); panel.hidden = !open; }
  btn.addEventListener('click', function () { set(btn.getAttribute('aria-expanded') !== 'true'); });
  document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && !panel.hidden) { set(false); btn.focus(); }
  });
  document.addEventListener('click', function (e) {
    var path = e.composedPath();
    if (!panel.hidden && path.indexOf(panel) < 0 && path.indexOf(btn) < 0) set(false);
  });
});

About this set

A mini cart for a tea shop header and five ways to show the cart count. Mini-cart dropdown is a cart icon with a red count badge that opens a panel under the header with each item, its quantity and price, a remove button, the subtotal and View cart and Checkout buttons; it starts open so you can see it. Below it, Add to cart buttons on three product cards put an item in the panel or raise its quantity. Header badge styles shows the same count as a bubble on the icon, a dot, a pill with the running total, a plain text link and a tab bar icon, all updated together. The plain and Tailwind versions use a disclosure button with aria-expanded and aria-controls that closes on Escape or an outside click; the Bootstrap version uses Bootstrap’s dropdown plugin with auto-close set to outside, so remove buttons inside the panel do not close it. A short script clones a template line for new products, recalculates the subtotal in cents and makes the badge pop briefly. Each change is announced through a polite status region, so the new count is heard as well as seen.

What’s included

  • Cart dropdown with lines, remove buttons, subtotal and two actions
  • Add to cart buttons that add a line or raise its quantity
  • Five badge styles: bubble, dot, total pill, text and tab bar
  • Badge text is the real count, plus a polite announcement for every change
  • Short pop animation on the badge that respects reduced motion

Accessibility

Every cart link and button includes the count in its accessible name, for example Cart, 3 items, and where a badge is purely visual it is hidden from assistive technology. The dropdown button reports its state with aria-expanded, Escape closes it and returns focus, and each remove button is named for its product. Additions and removals are announced through a polite role="status" region, and the badge animation stops under prefers-reduced-motion.

Customise it

Change --mc-accent and --mc-badge on .mc for the button and badge colours, and the width on .mc-panel. In Bootstrap the panel is .dropdown-menu with the same variables; in Tailwind edit the mc-* colours in @theme and the mc-bump keyframes.