Pricing — Usage slider (price scales)
A usage-based pricing card with a range slider for monthly API calls that recomputes the bill in real time using tiered, stepped pricing. Dragging the slider names the current tier (Starter, Pro, Scale, Enterprise), shows included quota and per-call overage rate, and renders a base-plus-usage breakdown. A filled track with boundary tick marks, an aria-live price announcement, and a side-by-side tier reference round it out, all in vanilla JS.
MCP
Code
:root {
--brand: #5b5bf0;
--brand-d: #4646d6;
--brand-700: #3a3ab8;
--brand-50: #eef0ff;
--accent: #00b4a6;
--accent-soft: #d8f5f2;
--ink: #101322;
--ink-2: #3a4060;
--muted: #6c7393;
--bg: #f6f7fb;
--white: #ffffff;
--surface: #ffffff;
--line: rgba(16, 19, 34, 0.1);
--line-2: rgba(16, 19, 34, 0.16);
--ok: #2f9e6f;
--warn: #d98a2b;
--danger: #d4503e;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 20px;
--sh-1: 0 1px 2px rgba(16, 19, 34, 0.08);
--sh-2: 0 8px 24px rgba(16, 19, 34, 0.08);
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
font-family: "Inter", system-ui, -apple-system, sans-serif;
font-size: 15px;
line-height: 1.5;
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1, h2, h3, p, ul, dl, dd, dt, figure {
margin: 0;
}
ul {
padding: 0;
list-style: none;
}
button {
font: inherit;
cursor: pointer;
}
:focus-visible {
outline: 3px solid rgba(91, 91, 240, 0.45);
outline-offset: 2px;
border-radius: 6px;
}
/* ============ PAGE ============ */
.page {
max-width: 1080px;
margin: 0 auto;
padding: clamp(28px, 5vw, 64px) clamp(16px, 4vw, 40px) 72px;
}
.page__head {
max-width: 640px;
margin-bottom: clamp(28px, 4vw, 44px);
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12.5px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--brand-d);
background: var(--brand-50);
border: 1px solid rgba(91, 91, 240, 0.2);
padding: 5px 11px;
border-radius: 999px;
}
.eyebrow svg {
color: var(--accent);
}
.page__title {
margin: 16px 0 10px;
font-size: clamp(26px, 4vw, 38px);
font-weight: 800;
letter-spacing: -0.02em;
line-height: 1.1;
}
.page__sub {
color: var(--muted);
font-size: 15.5px;
max-width: 56ch;
}
/* ============ LAYOUT ============ */
.layout {
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
gap: clamp(20px, 3vw, 32px);
align-items: start;
}
/* ============ PRICING CARD ============ */
.card {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-2);
padding: 26px 26px 24px;
overflow: hidden;
}
.card__topbar {
position: absolute;
inset: 0 0 auto 0;
height: 4px;
background: linear-gradient(90deg, var(--brand), var(--accent));
}
.card__head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
margin-top: 6px;
}
.card__plan {
display: flex;
align-items: center;
gap: 12px;
}
.brand-dot {
display: grid;
place-items: center;
width: 40px;
height: 40px;
border-radius: 12px;
background: var(--brand-50);
color: var(--brand-d);
flex: none;
}
.card__brand {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
}
.card__name {
font-size: 18px;
font-weight: 700;
letter-spacing: -0.01em;
}
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11.5px;
font-weight: 700;
padding: 5px 9px;
border-radius: 999px;
white-space: nowrap;
}
.badge--pop {
color: var(--white);
background: linear-gradient(120deg, var(--brand), var(--brand-d));
box-shadow: 0 4px 12px rgba(91, 91, 240, 0.3);
}
/* Price */
.price {
display: flex;
align-items: baseline;
gap: 2px;
margin: 22px 0 4px;
}
.price__cur {
font-size: 24px;
font-weight: 700;
color: var(--ink-2);
transform: translateY(-10px);
}
.price__amount {
font-size: clamp(44px, 8vw, 56px);
font-weight: 800;
letter-spacing: -0.03em;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.price__per {
font-size: 16px;
font-weight: 600;
color: var(--muted);
margin-left: 4px;
}
.price__note {
font-size: 13px;
color: var(--muted);
}
@keyframes pricePop {
0% { transform: scale(1.07); }
100% { transform: scale(1); }
}
.price--bump .price__amount {
animation: pricePop 0.28s ease;
}
/* Slider */
.slider {
margin: 24px 0 4px;
padding-top: 20px;
border-top: 1px solid var(--line);
}
.slider__labels {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 14px;
}
.slider__caption {
font-size: 13.5px;
font-weight: 600;
color: var(--ink-2);
}
.slider__value {
font-size: 15px;
font-weight: 700;
font-variant-numeric: tabular-nums;
color: var(--brand-d);
background: var(--brand-50);
padding: 3px 10px;
border-radius: 999px;
}
.slider__track-wrap {
position: relative;
height: 28px;
display: flex;
align-items: center;
}
.slider__track-wrap::before {
content: "";
position: absolute;
left: 0;
right: 0;
height: 8px;
border-radius: 999px;
background: #e6e8f3;
}
.slider__fill {
position: absolute;
left: 0;
height: 8px;
border-radius: 999px;
background: linear-gradient(90deg, var(--brand), var(--accent));
width: 0;
pointer-events: none;
transition: width 0.05s linear;
}
.slider__ticks {
position: absolute;
left: 0;
right: 0;
height: 8px;
pointer-events: none;
}
.slider__tick {
position: absolute;
top: -3px;
width: 2px;
height: 14px;
border-radius: 2px;
background: rgba(16, 19, 34, 0.22);
transform: translateX(-50%);
}
.slider__tick--passed {
background: rgba(255, 255, 255, 0.85);
}
.slider__input {
position: relative;
z-index: 2;
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 28px;
margin: 0;
background: transparent;
cursor: pointer;
}
.slider__input::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--white);
border: 3px solid var(--brand);
box-shadow: var(--sh-1), 0 0 0 0 rgba(91, 91, 240, 0.18);
transition: box-shadow 0.15s ease, transform 0.1s ease;
}
.slider__input::-moz-range-thumb {
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--white);
border: 3px solid var(--brand);
box-shadow: var(--sh-1);
}
.slider__input:hover::-webkit-slider-thumb {
box-shadow: var(--sh-1), 0 0 0 6px rgba(91, 91, 240, 0.14);
}
.slider__input:active::-webkit-slider-thumb {
transform: scale(1.08);
}
.slider__input:focus-visible::-webkit-slider-thumb {
box-shadow: 0 0 0 4px rgba(91, 91, 240, 0.4);
}
.slider__input::-webkit-slider-runnable-track,
.slider__input::-moz-range-track {
background: transparent;
}
.slider__scale {
display: flex;
justify-content: space-between;
margin-top: 10px;
font-size: 11.5px;
font-weight: 500;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
/* Tier strip */
.tiers {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 6px;
margin: 22px 0 4px;
}
.tier {
display: flex;
flex-direction: column;
gap: 2px;
padding: 9px 10px;
border-radius: var(--r-sm);
border: 1px solid var(--line);
background: var(--bg);
transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.tier__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--line-2);
margin-bottom: 3px;
transition: background 0.15s ease, box-shadow 0.15s ease;
}
.tier__name {
font-size: 12.5px;
font-weight: 700;
color: var(--ink-2);
}
.tier__range {
font-size: 11px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.tier.is-active {
border-color: var(--brand);
background: var(--brand-50);
transform: translateY(-1px);
}
.tier.is-active .tier__dot {
background: var(--brand);
box-shadow: 0 0 0 3px rgba(91, 91, 240, 0.18);
}
.tier.is-active .tier__name {
color: var(--brand-700);
}
/* Breakdown */
.breakdown {
margin: 22px 0 0;
padding: 16px 16px 14px;
border-radius: var(--r-md);
background: var(--bg);
border: 1px solid var(--line);
}
.breakdown__title {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--muted);
margin-bottom: 12px;
}
.breakdown__list {
display: grid;
gap: 2px;
}
.breakdown__row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
padding: 7px 0;
font-size: 14px;
}
.breakdown__row dt {
display: flex;
flex-direction: column;
gap: 2px;
}
.breakdown__label {
font-weight: 500;
color: var(--ink-2);
}
.breakdown__sub {
font-size: 12px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.breakdown__row dd {
font-weight: 600;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.breakdown__row--total {
margin-top: 6px;
padding-top: 12px;
border-top: 1px dashed var(--line-2);
font-size: 15px;
}
.breakdown__row--total dt {
font-weight: 700;
color: var(--ink);
}
.breakdown__row--total dd {
font-weight: 800;
font-size: 18px;
color: var(--brand-d);
}
.breakdown__row[hidden] {
display: none;
}
.breakdown__rate {
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--line);
font-size: 12.5px;
color: var(--muted);
}
.breakdown__rate strong {
color: var(--ink-2);
font-variant-numeric: tabular-nums;
}
/* CTA */
.cta {
margin-top: 20px;
width: 100%;
border: none;
border-radius: var(--r-md);
padding: 14px 18px;
font-size: 15px;
font-weight: 700;
color: var(--white);
background: linear-gradient(120deg, var(--brand), var(--brand-d));
box-shadow: 0 6px 18px rgba(91, 91, 240, 0.28);
transition: transform 0.12s ease, box-shadow 0.15s ease, filter 0.15s ease;
}
.cta:hover {
transform: translateY(-1px);
box-shadow: 0 10px 24px rgba(91, 91, 240, 0.34);
filter: brightness(1.04);
}
.cta:active {
transform: translateY(0);
box-shadow: 0 4px 12px rgba(91, 91, 240, 0.28);
}
.card__fineprint {
margin-top: 12px;
text-align: center;
font-size: 12px;
color: var(--muted);
}
/* ============ REFERENCE ============ */
.ref {
position: sticky;
top: 24px;
}
.ref__title {
font-size: 18px;
font-weight: 700;
letter-spacing: -0.01em;
}
.ref__sub {
margin: 8px 0 18px;
font-size: 13.5px;
color: var(--muted);
max-width: 44ch;
}
.ref__list {
display: grid;
gap: 12px;
}
.ref-card {
position: relative;
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--sh-1);
padding: 16px 16px 14px;
transition: border-color 0.15s ease, box-shadow 0.18s ease, transform 0.18s ease;
}
.ref-card--pop {
border-color: rgba(91, 91, 240, 0.4);
}
.ref-card__tag {
position: absolute;
top: -9px;
right: 14px;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
color: var(--brand-d);
background: var(--brand-50);
border: 1px solid rgba(91, 91, 240, 0.3);
padding: 3px 8px;
border-radius: 999px;
}
.ref-card.is-active {
border-color: var(--brand);
box-shadow: 0 0 0 3px rgba(91, 91, 240, 0.16), var(--sh-2);
transform: translateY(-2px);
}
.ref-card__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
}
.ref-card__name {
font-size: 16px;
font-weight: 700;
}
.ref-card__price {
font-size: 13px;
font-weight: 700;
color: var(--brand-d);
font-variant-numeric: tabular-nums;
}
.ref-card__quota {
margin-top: 6px;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
}
.ref-card__rate {
font-size: 12.5px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.ref-card__feats {
margin-top: 11px;
padding-top: 11px;
border-top: 1px solid var(--line);
display: grid;
gap: 6px;
}
.ref-card__feats li {
position: relative;
padding-left: 22px;
font-size: 13px;
color: var(--ink-2);
}
.ref-card__feats li::before {
content: "";
position: absolute;
left: 0;
top: 1px;
width: 16px;
height: 16px;
border-radius: 50%;
background:
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 13l4 4L19 6' fill='none' stroke='%2300b4a6' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E")
center / 12px no-repeat,
var(--accent-soft);
}
/* ============ TOAST ============ */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%) translateY(14px);
background: var(--ink);
color: var(--white);
font-size: 13.5px;
font-weight: 600;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--sh-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
z-index: 50;
max-width: calc(100vw - 32px);
}
.toast.is-show {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
/* ============ RESPONSIVE ============ */
@media (max-width: 880px) {
.layout {
grid-template-columns: 1fr;
}
.ref {
position: static;
}
.ref__list {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 520px) {
body {
font-size: 14.5px;
}
.card {
padding: 22px 18px 20px;
}
.tiers {
grid-template-columns: repeat(2, 1fr);
}
.ref__list {
grid-template-columns: 1fr;
}
.card__head {
flex-direction: column;
}
.badge--pop {
align-self: flex-start;
}
.price__amount {
font-size: 44px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
}(function () {
"use strict";
/* ------------------------------------------------------------------ *
* Tiered / stepped pricing model for Northwind Cloud (API calls).
* Each tier defines:
* - id, label
* - from / to: the usage window (calls) where this tier is the
* "current" tier shown to the user
* - base: monthly base fee for that plan
* - included: calls included in the base fee (no overage)
* - rate: $ per call charged on usage ABOVE `included`
* - popular: highlight flag
* ------------------------------------------------------------------ */
var TIERS = [
{ id: "starter", label: "Starter", from: 0, to: 50000, base: 0, included: 50000, rate: 0.00220, popular: false },
{ id: "pro", label: "Pro", from: 50000, to: 250000, base: 99, included: 50000, rate: 0.00196, popular: true },
{ id: "scale", label: "Scale", from: 250000, to: 750000, base: 399, included: 250000, rate: 0.00150, popular: false },
{ id: "enterprise", label: "Enterprise", from: 750000, to: Infinity, base: 1200, included: 750000, rate: 0.00110, popular: false }
];
var MAX = 1000000;
/* ----- DOM ----- */
var slider = document.getElementById("usage");
var usageOut = document.getElementById("usageOut");
var fill = document.querySelector("[data-fill]");
var ticksWrap = document.querySelector("[data-ticks]");
var priceLive = document.getElementById("priceLive");
var priceEl = document.querySelector("[data-price]");
var priceNote = document.querySelector("[data-price-note]");
var tierNameEls = document.querySelectorAll("[data-tier-name]");
var popularBadge = document.getElementById("popularBadge");
var baseLabel = document.querySelector("[data-base-label]");
var baseEl = document.querySelector("[data-base]");
var includedEl = document.querySelector("[data-included]");
var includedPriceEl = document.querySelector("[data-included-price]");
var overageRow = document.querySelector("[data-overage-row]");
var overageDetail = document.querySelector("[data-overage-detail]");
var overageEl = document.querySelector("[data-overage]");
var totalEl = document.querySelector("[data-total]");
var rateEl = document.querySelector("[data-rate]");
var tierStrip = document.getElementById("tierStrip");
var refCards = document.querySelectorAll(".ref-card");
var startBtn = document.getElementById("startBtn");
var toastEl = document.getElementById("toast");
/* ----- formatters ----- */
var nf = new Intl.NumberFormat("en-US");
function money(n) {
return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function moneyRound(n) {
return "$" + Math.round(n).toLocaleString("en-US");
}
function rateStr(r) {
return "$" + r.toFixed(5);
}
/* ----- find the active tier for a usage value ----- */
function tierFor(usage) {
for (var i = 0; i < TIERS.length; i++) {
if (usage >= TIERS[i].from && usage < TIERS[i].to) return TIERS[i];
}
return TIERS[TIERS.length - 1];
}
/* ----- compute price for usage within its tier ----- */
function compute(usage) {
var t = tierFor(usage);
var overUnits = Math.max(0, usage - t.included);
var overageCost = overUnits * t.rate;
var total = t.base + overageCost;
return {
tier: t,
overUnits: overUnits,
overageCost: overageCost,
total: total
};
}
/* ----- ticks at each tier boundary ----- */
function buildTicks() {
var bounds = [50000, 250000, 750000];
bounds.forEach(function (b) {
var span = document.createElement("span");
span.className = "slider__tick";
span.style.left = (b / MAX) * 100 + "%";
span.dataset.at = String(b);
ticksWrap.appendChild(span);
});
}
/* ----- toast helper ----- */
var toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.hidden = false;
/* reflow to restart transition */
void toastEl.offsetWidth;
toastEl.classList.add("is-show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
toastEl.classList.remove("is-show");
setTimeout(function () { toastEl.hidden = true; }, 250);
}, 2600);
}
/* ----- render ----- */
var lastTierId = null;
function render(bump) {
var usage = parseInt(slider.value, 10) || 0;
var pct = (usage / MAX) * 100;
var r = compute(usage);
var t = r.tier;
/* slider visuals */
fill.style.width = pct + "%";
Array.prototype.forEach.call(ticksWrap.children, function (tick) {
var at = parseInt(tick.dataset.at, 10);
tick.classList.toggle("slider__tick--passed", usage >= at);
});
/* usage label */
var usageLabel = usage >= MAX ? nf.format(MAX) + "+" : nf.format(usage);
usageOut.textContent = usageLabel;
/* price */
priceEl.textContent = moneyRound(r.total).replace("$", "");
priceNote.textContent = "Estimated for " + usageLabel + " monthly API calls";
/* tier name + popular badge */
tierNameEls.forEach(function (el) { el.textContent = t.label; });
popularBadge.hidden = !t.popular;
/* breakdown */
baseLabel.textContent = t.label + " base fee";
baseEl.textContent = money(t.base);
includedEl.textContent = nf.format(t.included) + " calls included";
includedPriceEl.textContent = money(0);
if (r.overUnits > 0) {
overageRow.hidden = false;
overageDetail.textContent = nf.format(r.overUnits) + " × " + rateStr(t.rate);
overageEl.textContent = money(r.overageCost);
} else {
overageRow.hidden = true;
}
totalEl.textContent = money(r.total);
rateEl.textContent = rateStr(t.rate);
/* tier strip + reference highlight */
Array.prototype.forEach.call(tierStrip.children, function (li) {
li.classList.toggle("is-active", li.dataset.tier === t.id);
});
refCards.forEach(function (card) {
card.classList.toggle("is-active", card.dataset.ref === t.id);
});
/* ARIA on slider */
slider.setAttribute("aria-valuenow", String(usage));
var valueText =
usageLabel + " monthly API calls — " +
t.label + " plan, " + money(r.total) + " per month";
slider.setAttribute("aria-valuetext", valueText);
/* price bump micro-animation */
if (bump) {
priceLive.classList.remove("price--bump");
void priceLive.offsetWidth;
priceLive.classList.add("price--bump");
}
/* announce tier changes (debounced via lastTierId) */
if (lastTierId !== null && lastTierId !== t.id) {
toast("Now on the " + t.label + " tier · " + money(r.total) + "/mo");
}
lastTierId = t.id;
}
/* ----- events ----- */
var rafPending = false;
slider.addEventListener("input", function () {
if (rafPending) return;
rafPending = true;
requestAnimationFrame(function () {
rafPending = false;
render(true);
});
});
/* keyboard: PageUp/Pagedown jump a tier-friendly step (native), but
add Home/End helpers + announce on change handled by render() */
slider.addEventListener("change", function () { render(true); });
/* clicking a reference card or tier chip snaps the slider into that
tier's range (midpoint of the included→to window) */
function snapToTier(id) {
var t = TIERS.filter(function (x) { return x.id === id; })[0];
if (!t) return;
var hi = t.to === Infinity ? MAX : t.to;
var mid = Math.round((t.from + hi) / 2 / 5000) * 5000;
slider.value = String(Math.min(MAX, mid));
render(true);
slider.focus();
}
refCards.forEach(function (card) {
card.addEventListener("click", function () { snapToTier(card.dataset.ref); });
});
Array.prototype.forEach.call(tierStrip.children, function (li) {
li.style.cursor = "pointer";
li.setAttribute("role", "button");
li.setAttribute("tabindex", "0");
li.addEventListener("click", function () { snapToTier(li.dataset.tier); });
li.addEventListener("keydown", function (e) {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
snapToTier(li.dataset.tier);
}
});
});
startBtn.addEventListener("click", function () {
var usage = parseInt(slider.value, 10) || 0;
var r = compute(usage);
toast(
"Trial started on " + r.tier.label + " — est. " +
money(r.total) + "/mo for " + nf.format(usage) + " calls"
);
});
/* ----- init ----- */
buildTicks();
render(false);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Northwind — Usage-based pricing slider</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="page">
<header class="page__head">
<span class="eyebrow">
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
<path d="M3 13l4 4L21 5" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Pay for what you use
</span>
<h1 class="page__title">Scale pricing that grows with you</h1>
<p class="page__sub">
Drag the slider to estimate your monthly bill. Pricing is tiered — you only pay the
overage rate on usage above your included quota.
</p>
</header>
<section class="layout">
<!-- ============ PRICING CARD ============ -->
<article class="card" aria-labelledby="plan-name">
<div class="card__topbar" aria-hidden="true"></div>
<header class="card__head">
<div class="card__plan">
<span class="brand-dot" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16">
<path d="M12 2l2.6 6.3L21 9l-4.8 4.2L17.6 20 12 16.4 6.4 20l1.4-6.8L3 9l6.4-.7z"
fill="currentColor"/>
</svg>
</span>
<div>
<p class="card__brand">Northwind Cloud</p>
<h2 class="card__name" id="plan-name" aria-live="polite">
<span data-tier-name>Pro</span> plan
</h2>
</div>
</div>
<span class="badge badge--pop" id="popularBadge" hidden>
<svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true">
<path d="M12 2l2.6 6.3L21 9l-4.8 4.2L17.6 20 12 16.4 6.4 20l1.4-6.8L3 9l6.4-.7z" fill="currentColor"/>
</svg>
Most popular
</span>
</header>
<!-- Live price -->
<div class="price" aria-live="polite" id="priceLive">
<span class="price__cur">$</span>
<span class="price__amount" data-price>148</span>
<span class="price__per">/ mo</span>
</div>
<p class="price__note" data-price-note>Estimated for 75,000 monthly API calls</p>
<!-- Slider -->
<div class="slider">
<div class="slider__labels">
<label class="slider__caption" for="usage">Monthly API calls</label>
<output class="slider__value" id="usageOut" for="usage">75,000</output>
</div>
<div class="slider__track-wrap">
<div class="slider__fill" data-fill aria-hidden="true"></div>
<input
type="range"
id="usage"
class="slider__input"
min="0"
max="1000000"
step="5000"
value="75000"
aria-label="Monthly API calls"
aria-valuemin="0"
aria-valuemax="1000000"
aria-valuenow="75000"
aria-valuetext="75,000 monthly API calls — Pro plan, $148 per month"
/>
<div class="slider__ticks" data-ticks aria-hidden="true"></div>
</div>
<div class="slider__scale" aria-hidden="true">
<span>0</span><span>250K</span><span>500K</span><span>750K</span><span>1M+</span>
</div>
</div>
<!-- Tier strip -->
<ul class="tiers" id="tierStrip" aria-label="Pricing tiers">
<li class="tier" data-tier="starter">
<span class="tier__dot" aria-hidden="true"></span>
<span class="tier__name">Starter</span>
<span class="tier__range">0–50K</span>
</li>
<li class="tier" data-tier="pro">
<span class="tier__dot" aria-hidden="true"></span>
<span class="tier__name">Pro</span>
<span class="tier__range">50K–250K</span>
</li>
<li class="tier" data-tier="scale">
<span class="tier__dot" aria-hidden="true"></span>
<span class="tier__name">Scale</span>
<span class="tier__range">250K–750K</span>
</li>
<li class="tier" data-tier="enterprise">
<span class="tier__dot" aria-hidden="true"></span>
<span class="tier__name">Enterprise</span>
<span class="tier__range">750K+</span>
</li>
</ul>
<!-- Breakdown -->
<div class="breakdown" aria-live="polite">
<h3 class="breakdown__title">Your estimate</h3>
<dl class="breakdown__list">
<div class="breakdown__row">
<dt><span class="breakdown__label" data-base-label>Pro base fee</span></dt>
<dd data-base>$99.00</dd>
</div>
<div class="breakdown__row">
<dt>
<span class="breakdown__label">Included quota</span>
<span class="breakdown__sub" data-included>50,000 calls included</span>
</dt>
<dd data-included-price>$0.00</dd>
</div>
<div class="breakdown__row" data-overage-row>
<dt>
<span class="breakdown__label">Overage usage</span>
<span class="breakdown__sub" data-overage-detail>25,000 × $0.00196</span>
</dt>
<dd data-overage>$49.00</dd>
</div>
<div class="breakdown__row breakdown__row--total">
<dt>Estimated monthly total</dt>
<dd data-total>$148.00</dd>
</div>
</dl>
<p class="breakdown__rate">
Overage rate at this tier:
<strong data-rate>$0.00196</strong> / call
</p>
</div>
<button class="cta" type="button" id="startBtn">
Start with <span data-tier-name>Pro</span>
</button>
<p class="card__fineprint">No credit card required for the 14-day trial. Cancel anytime.</p>
</article>
<!-- ============ TIER REFERENCE ============ -->
<aside class="ref" aria-label="Tier reference">
<h2 class="ref__title">How tiers stack up</h2>
<p class="ref__sub">
Each tier adds a larger included quota and a lower marginal overage rate. Higher
volume always means a lower price per call.
</p>
<ul class="ref__list" id="refList">
<li class="ref-card" data-ref="starter">
<header class="ref-card__head">
<h3 class="ref-card__name">Starter</h3>
<span class="ref-card__price">$0 base</span>
</header>
<p class="ref-card__quota">50,000 calls included</p>
<p class="ref-card__rate">then $0.00220 / call</p>
<ul class="ref-card__feats">
<li>Community support</li>
<li>1 project</li>
<li>7-day log retention</li>
</ul>
</li>
<li class="ref-card ref-card--pop" data-ref="pro">
<span class="ref-card__tag">Most popular</span>
<header class="ref-card__head">
<h3 class="ref-card__name">Pro</h3>
<span class="ref-card__price">$99 base</span>
</header>
<p class="ref-card__quota">50,000 calls included</p>
<p class="ref-card__rate">then $0.00196 / call</p>
<ul class="ref-card__feats">
<li>Priority email support</li>
<li>10 projects</li>
<li>30-day log retention</li>
<li>Usage alerts & budgets</li>
</ul>
</li>
<li class="ref-card" data-ref="scale">
<header class="ref-card__head">
<h3 class="ref-card__name">Scale</h3>
<span class="ref-card__price">$399 base</span>
</header>
<p class="ref-card__quota">250,000 calls included</p>
<p class="ref-card__rate">then $0.00150 / call</p>
<ul class="ref-card__feats">
<li>24/5 support & SLA</li>
<li>Unlimited projects</li>
<li>90-day log retention</li>
<li>SAML SSO</li>
</ul>
</li>
<li class="ref-card" data-ref="enterprise">
<header class="ref-card__head">
<h3 class="ref-card__name">Enterprise</h3>
<span class="ref-card__price">$1,200 base</span>
</header>
<p class="ref-card__quota">750,000 calls included</p>
<p class="ref-card__rate">then $0.00110 / call</p>
<ul class="ref-card__feats">
<li>Dedicated CSM</li>
<li>Custom SLA & uptime</li>
<li>1-year log retention</li>
<li>On-prem & VPC peering</li>
</ul>
</li>
</ul>
</aside>
</section>
</main>
<!-- Toast -->
<div class="toast" id="toast" role="status" aria-live="polite" hidden></div>
<script src="script.js"></script>
</body>
</html>Usage slider (price scales)
A single pricing card for the fictional Northwind Cloud where the customer drags a range
slider to set their expected monthly API-call volume. The price recalculates live with a
tiered, stepped model: each tier (Starter, Pro, Scale, Enterprise) carries its own base fee,
included quota, and per-call overage rate, so the marginal cost drops as volume grows. The big
headline price bumps on change, the current tier name updates, and a base-plus-usage breakdown
lists the base fee, included quota, and any overage as units × rate.
The slider has a gradient-filled track and tick marks at the three tier boundaries (50K, 250K,
750K). It exposes aria-valuenow and a descriptive aria-valuetext that includes the tier and
computed price, while an aria-live region announces the new total as you drag. Crossing a tier
boundary fires a small toast. A four-chip tier strip and a side panel of tier reference cards
(with a “Most popular” Pro highlight) stay in sync with the slider, and both are clickable to
snap the slider into that tier’s range.
Everything is vanilla JS with no dependencies: the price math, formatting (Intl.NumberFormat),
tick generation, and highlight syncing run from a single tier table. Interactions are keyboard
usable with focus-visible rings, the tier chips act as buttons, and the layout reflows to a
single column down to ~360px.