CTA Banners
Free shipping progress bar
How far the basket is from the threshold
How far the basket is from the threshold
Create a shopping incentive bar with Tailwind CSS. Use a max-w-2xl bordered rounded-2xl card. The top row pairs a small van-like glyph tile with a sentence reading how much more is needed for free delivery, the remaining amount emphasised in medium weight, and the current basket total on the right in monospace. Under it, a thin track with a neutral-900 fill at seventy-two percent carrying three tick markers with tiny labels beneath — free delivery, free returns and a gift, the first two below the current position and rendered in green with check glyphs. Then a horizontal strip of three suggested products to close the gap, each a compact bordered tile with a small square placeholder, a name, a price and an "Add" text button. Finish with a muted line noting the delivery estimate and a link to the returns policy. Then add JavaScript that adds a product price to the basket when Add is clicked, moves the progress fill, rewrites the sentence, and switches it to a green "You have unlocked free delivery" state once the threshold is passed. <section class="mx-auto max-w-2xl px-6 py-14">
<div class="rounded-2xl border border-neutral-200 bg-white p-6">
<div class="flex items-start gap-3.5">
<span class="grid h-10 w-10 shrink-0 place-items-center rounded-xl bg-neutral-100 text-neutral-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M3 7.5A2.5 2.5 0 0 1 5.5 5H9l2 2h7.5A2.5 2.5 0 0 1 21 9.5v7A2.5 2.5 0 0 1 18.5 19h-13A2.5 2.5 0 0 1 3 16.5v-9Z"/></svg></span>
<div class="min-w-0 flex-1">
<p id="shipMessage" class="text-sm text-neutral-700">Spend <span class="font-semibold text-neutral-900">€14.00</span> more for free delivery</p>
<p class="mt-0.5 text-xs text-neutral-500">Standard delivery is €4.95 below the threshold.</p>
</div>
<p id="shipTotal" class="shrink-0 font-mono text-sm font-medium text-neutral-900">€36.00</p>
</div>
<div class="mt-6">
<div class="h-2 overflow-hidden rounded-full bg-neutral-100">
<div id="shipFill" class="h-full rounded-full bg-neutral-900 transition-[width] duration-500 ease-out" style="width:72%"></div>
</div>
<div class="mt-2 flex justify-between text-[11px]">
<span class="flex items-center gap-1 text-green-700"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="m5 12.5 4.25 4.25L19 7"/></svg> €25 free returns</span>
<span id="shipTick50" class="flex items-center gap-1 text-neutral-400">€50 free delivery</span>
<span class="text-neutral-400">€90 gift</span>
</div>
</div>
<p class="mt-7 text-xs font-semibold uppercase tracking-widest text-neutral-400">Small things that close the gap</p>
<div class="mt-3 grid gap-3 sm:grid-cols-3">
<div class="rounded-xl border border-neutral-200 p-3">
<div class="grid h-16 place-items-center rounded-lg bg-neutral-100 text-neutral-400"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="m12 3 9 9-9 9-9-9 9-9Z"/><path d="m12 7 5 5-5 5-5-5 5-5Z"/></svg></div>
<p class="mt-2.5 text-sm font-medium text-neutral-900">Bench scraper</p>
<div class="mt-1 flex items-center justify-between">
<span class="font-mono text-xs text-neutral-500">€12.00</span>
<button class="add text-xs font-medium text-neutral-900 underline-offset-4 hover:underline" data-price="12">Add</button>
</div>
</div>
<div class="rounded-xl border border-neutral-200 p-3">
<div class="grid h-16 place-items-center rounded-lg bg-neutral-100 text-neutral-400"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="m12 3 9 5-9 5-9-5 9-5Z"/><path d="m3 12 9 5 9-5M3 16l9 5 9-5"/></svg></div>
<p class="mt-2.5 text-sm font-medium text-neutral-900">Linen cloth</p>
<div class="mt-1 flex items-center justify-between">
<span class="font-mono text-xs text-neutral-500">€9.50</span>
<button class="add text-xs font-medium text-neutral-900 underline-offset-4 hover:underline" data-price="9.5">Add</button>
</div>
</div>
<div class="rounded-xl border border-neutral-200 p-3">
<div class="grid h-16 place-items-center rounded-lg bg-neutral-100 text-neutral-400"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M4 20V10m5 10V4m6 16v-7m5 7V7"/></svg></div>
<p class="mt-2.5 text-sm font-medium text-neutral-900">Sharpening stone</p>
<div class="mt-1 flex items-center justify-between">
<span class="font-mono text-xs text-neutral-500">€18.00</span>
<button class="add text-xs font-medium text-neutral-900 underline-offset-4 hover:underline" data-price="18">Add</button>
</div>
</div>
</div>
<p class="mt-5 border-t border-neutral-200 pt-4 text-xs text-neutral-400">
Ordered before 15:00 on a weekday, it leaves the same day. Returns are free for 60 days — <a href="#" class="underline underline-offset-2 hover:text-neutral-700">the policy</a> is two paragraphs long.
</p>
</div>
</section> var THRESHOLD = 50;
var total = 36;
var message = document.getElementById("shipMessage");
var totalEl = document.getElementById("shipTotal");
var fill = document.getElementById("shipFill");
var tick = document.getElementById("shipTick50");
function money(n) {
return "€" + n.toFixed(2);
}
function render() {
totalEl.textContent = money(total);
// The bar keeps filling past the threshold, but never past its own width.
fill.style.width = Math.min((total / THRESHOLD) * 100, 100) + "%";
if (total >= THRESHOLD) {
message.innerHTML = '<span class="font-semibold text-green-700">Free delivery unlocked.</span> Nothing more to add.';
fill.classList.remove("bg-neutral-900");
fill.classList.add("bg-green-600");
tick.className = "flex items-center gap-1 text-green-700";
tick.textContent = "✓ €50 free delivery";
return;
}
message.innerHTML =
"Spend <span class=\"font-semibold text-neutral-900\">" + money(THRESHOLD - total) + "</span> more for free delivery";
}
Array.prototype.slice.call(document.querySelectorAll(".add")).forEach(function (button) {
button.addEventListener("click", function () {
total += Number(button.dataset.price);
button.textContent = "Added";
button.disabled = true;
button.classList.add("text-neutral-400", "no-underline");
render();
});
});
render();