Usage Slider Pricing
Original · freeusage-based pricing with a slider
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/pricex-usage-slider.json"use client";
import { useEffect, useState, type ChangeEvent } from "react";
import {
motion,
useReducedMotion,
useSpring,
useMotionValueEvent,
} from "motion/react";
/* ----------------------------------------------------------------------------
* Pricing model — usage-metered API platform ("Meterly")
* -------------------------------------------------------------------------- */
const STOPS: readonly number[] = [
25_000, 50_000, 100_000, 250_000, 500_000, 1_000_000, 2_000_000, 5_000_000,
10_000_000, 20_000_000, 50_000_000,
];
const DEFAULT_INDEX = 5;
const ANNUAL_DISCOUNT = 0.17;
type TierKey = "free" | "starter" | "growth" | "scale";
interface TierInfo {
key: TierKey;
name: string;
base: number;
badge: string;
dot: string;
features: string[];
}
const TIERS: Record<TierKey, TierInfo> = {
free: {
key: "free",
name: "Free",
base: 0,
badge:
"bg-slate-100 text-slate-700 ring-slate-300/70 dark:bg-slate-800 dark:text-slate-200 dark:ring-slate-600/70",
dot: "bg-slate-400",
features: [
"100K requests included every month",
"3 active projects",
"Community support",
"7-day log retention",
],
},
starter: {
key: "starter",
name: "Starter",
base: 19,
badge:
"bg-sky-100 text-sky-700 ring-sky-300/70 dark:bg-sky-950 dark:text-sky-300 dark:ring-sky-800/70",
dot: "bg-sky-500",
features: [
"Unlimited projects & API keys",
"Email support · 24h response",
"Usage alerts & spend budgets",
"30-day log retention",
],
},
growth: {
key: "growth",
name: "Growth",
base: 49,
badge:
"bg-indigo-100 text-indigo-700 ring-indigo-300/70 dark:bg-indigo-950 dark:text-indigo-300 dark:ring-indigo-800/70",
dot: "bg-indigo-500",
features: [
"SSO & granular team roles",
"Priority support · 4h SLA",
"99.9% uptime guarantee",
"90-day log retention",
],
},
scale: {
key: "scale",
name: "Scale",
base: 99,
badge:
"bg-emerald-100 text-emerald-700 ring-emerald-300/70 dark:bg-emerald-950 dark:text-emerald-300 dark:ring-emerald-800/70",
dot: "bg-emerald-500",
features: [
"Dedicated success manager",
"99.99% uptime SLA",
"Private networking & custom limits",
"1-year log retention",
],
},
};
function tierFor(requests: number): TierInfo {
if (requests < 100_000) return TIERS.free;
if (requests < 2_000_000) return TIERS.starter;
if (requests < 20_000_000) return TIERS.growth;
return TIERS.scale;
}
function meteredCost(requests: number): number {
const brackets: { upTo: number; per1k: number }[] = [
{ upTo: 100_000, per1k: 0 },
{ upTo: 2_000_000, per1k: 0.2 },
{ upTo: 20_000_000, per1k: 0.1 },
{ upTo: Number.POSITIVE_INFINITY, per1k: 0.05 },
];
let prev = 0;
let cost = 0;
for (const b of brackets) {
const cap = Math.min(requests, b.upTo);
const portion = cap - prev;
if (portion > 0) cost += (portion / 1000) * b.per1k;
prev = b.upTo;
if (requests <= b.upTo) break;
}
return cost;
}
function totalFor(requests: number): number {
return tierFor(requests).base + meteredCost(requests);
}
function formatRequests(n: number): string {
if (n >= 1_000_000) {
const m = n / 1_000_000;
return `${Number.isInteger(m) ? m : m.toFixed(1)}M`;
}
if (n >= 1_000) return `${Math.round(n / 1_000)}K`;
return `${n}`;
}
function formatMoney(v: number): string {
return `$${Math.round(v).toLocaleString("en-US")}`;
}
const INITIAL_MONTHLY = totalFor(STOPS[DEFAULT_INDEX]);
/* ----------------------------------------------------------------------------
* Inline icons
* -------------------------------------------------------------------------- */
function CheckIcon() {
return (
<svg
viewBox="0 0 20 20"
fill="none"
className="h-3.5 w-3.5"
aria-hidden="true"
>
<path
d="M4.5 10.5l3.4 3.4L15.5 6.5"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function BoltIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-4 w-4" aria-hidden="true">
<path
d="M13 2L4 14h6l-1 8 9-12h-6l1-8z"
stroke="currentColor"
strokeWidth="1.6"
strokeLinejoin="round"
/>
</svg>
);
}
function ArrowIcon() {
return (
<svg viewBox="0 0 20 20" fill="none" className="h-4 w-4" aria-hidden="true">
<path
d="M4 10h11m0 0l-4-4m4 4l-4 4"
stroke="currentColor"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
/* ----------------------------------------------------------------------------
* Component
* -------------------------------------------------------------------------- */
type Billing = "monthly" | "annual";
export default function PricexUsageSlider() {
const reduce = useReducedMotion();
const [index, setIndex] = useState<number>(DEFAULT_INDEX);
const [billing, setBilling] = useState<Billing>("monthly");
const [priceLabel, setPriceLabel] = useState<string>(() =>
formatMoney(INITIAL_MONTHLY),
);
const requests = STOPS[index];
const tier = tierFor(requests);
const usageCost = meteredCost(requests);
const monthly = tier.base + usageCost;
const annualMonthly = monthly * (1 - ANNUAL_DISCOUNT);
const effectiveMonthly = billing === "annual" ? annualMonthly : monthly;
const yearlyTotal = annualMonthly * 12;
const yearlySaved = monthly * 12 - yearlyTotal;
const pct = (index / (STOPS.length - 1)) * 100;
const priceSpring = useSpring(INITIAL_MONTHLY, {
stiffness: 150,
damping: 24,
mass: 0.6,
});
useEffect(() => {
if (reduce) priceSpring.jump(effectiveMonthly);
else priceSpring.set(effectiveMonthly);
}, [effectiveMonthly, reduce, priceSpring]);
useMotionValueEvent(priceSpring, "change", (v) => {
setPriceLabel(formatMoney(v));
});
const onSlide = (e: ChangeEvent<HTMLInputElement>) => {
setIndex(Number(e.target.value));
};
const fillTransition = reduce
? { duration: 0 }
: ({ type: "spring", stiffness: 210, damping: 30 } as const);
const pillTransition = reduce
? { duration: 0 }
: ({ type: "spring", stiffness: 420, damping: 34 } as const);
return (
<section className="relative w-full overflow-hidden bg-slate-50 px-6 py-24 text-slate-900 sm:py-28 dark:bg-slate-950 dark:text-slate-100">
<style>{`
@keyframes pxus-float {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(0, -26px, 0) scale(1.06); }
}
@keyframes pxus-drift {
0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
50% { transform: translate3d(24px, 18px, 0) scale(1.08); }
}
@keyframes pxus-glow {
0%, 100% { opacity: 0.35; }
50% { opacity: 0.7; }
}
.pxus-blob-a { animation: pxus-float 17s ease-in-out infinite; }
.pxus-blob-b { animation: pxus-drift 21s ease-in-out infinite; }
.pxus-glow { animation: pxus-glow 3.4s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.pxus-blob-a, .pxus-blob-b, .pxus-glow { animation: none !important; }
}
`}</style>
{/* Atmospheric background */}
<div
className="pxus-blob-a pointer-events-none absolute -left-24 -top-24 h-80 w-80 rounded-full bg-indigo-400/25 blur-3xl dark:bg-indigo-600/20"
aria-hidden="true"
/>
<div
className="pxus-blob-b pointer-events-none absolute -bottom-32 -right-16 h-96 w-96 rounded-full bg-violet-400/20 blur-3xl dark:bg-violet-700/20"
aria-hidden="true"
/>
<div
className="pointer-events-none absolute inset-0 opacity-[0.4] [background-image:linear-gradient(to_right,rgba(100,116,139,0.08)_1px,transparent_1px),linear-gradient(to_bottom,rgba(100,116,139,0.08)_1px,transparent_1px)] [background-size:44px_44px] [mask-image:radial-gradient(ellipse_at_center,black,transparent_75%)]"
aria-hidden="true"
/>
<div className="relative mx-auto max-w-6xl">
{/* Header */}
<div className="mx-auto max-w-2xl text-center">
<span className="inline-flex items-center gap-1.5 rounded-full bg-white/70 px-3.5 py-1.5 text-xs font-medium uppercase tracking-[0.14em] text-indigo-700 ring-1 ring-indigo-200/80 backdrop-blur dark:bg-white/5 dark:text-indigo-300 dark:ring-indigo-500/25">
<span className="text-indigo-500 dark:text-indigo-400">
<BoltIcon />
</span>
Usage-based pricing
</span>
<h2 className="mt-6 text-balance text-4xl font-semibold tracking-tight sm:text-5xl">
Pay only for the requests you actually make.
</h2>
<p className="mx-auto mt-4 max-w-xl text-pretty text-base leading-relaxed text-slate-600 dark:text-slate-400">
No per-seat tax, no overage surprises. Drag the slider to size your
monthly volume and Meterly meters it to the request — billed
transparently, down to the last call.
</p>
</div>
{/* Card */}
<div className="mt-14 overflow-hidden rounded-3xl border border-slate-200/80 bg-white/80 shadow-[0_24px_70px_-30px_rgba(30,41,59,0.4)] backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/70 dark:shadow-[0_24px_80px_-30px_rgba(0,0,0,0.8)]">
{/* Toggle row */}
<div className="flex flex-col gap-4 border-b border-slate-200/70 px-6 py-5 sm:flex-row sm:items-center sm:justify-between sm:px-9 dark:border-white/10">
<p className="text-sm font-medium text-slate-500 dark:text-slate-400">
Estimate your monthly bill
</p>
<div
role="group"
aria-label="Billing period"
className="inline-flex items-center rounded-full bg-slate-100 p-1 text-sm font-medium ring-1 ring-slate-200/70 dark:bg-slate-800/80 dark:ring-white/10"
>
{(["monthly", "annual"] as const).map((opt) => {
const active = billing === opt;
return (
<button
key={opt}
type="button"
onClick={() => setBilling(opt)}
aria-pressed={active}
className={`relative rounded-full px-4 py-1.5 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-100 dark:focus-visible:ring-offset-slate-800 ${
active
? "text-slate-900 dark:text-white"
: "text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
}`}
>
{active && (
<motion.span
layoutId="pxus-billing-pill"
transition={pillTransition}
className="absolute inset-0 rounded-full bg-white shadow-sm ring-1 ring-slate-200/80 dark:bg-slate-700 dark:ring-white/10"
/>
)}
<span className="relative z-10 flex items-center gap-1.5 capitalize">
{opt}
{opt === "annual" && (
<span className="rounded-full bg-emerald-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300">
-17%
</span>
)}
</span>
</button>
);
})}
</div>
</div>
<div className="grid gap-px bg-slate-200/60 lg:grid-cols-[1.15fr_0.85fr] dark:bg-white/10">
{/* Left — slider + features */}
<div className="bg-white/80 p-6 sm:p-9 dark:bg-slate-900/70">
<div className="flex items-end justify-between gap-4">
<div>
<p className="text-sm font-medium text-slate-500 dark:text-slate-400">
Monthly API requests
</p>
<p
aria-live="polite"
className="mt-1 text-4xl font-semibold tracking-tight tabular-nums sm:text-5xl"
>
{formatRequests(requests)}
<span className="ml-1 text-lg font-normal text-slate-400 dark:text-slate-500">
/ mo
</span>
</p>
</div>
<span
className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-semibold ring-1 ${tier.badge}`}
>
<span className={`h-1.5 w-1.5 rounded-full ${tier.dot}`} />
{tier.name} tier
</span>
</div>
{/* Slider */}
<div className="mt-8">
<div className="relative h-6">
<div className="absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-slate-200 dark:bg-slate-700" />
<motion.div
className="pointer-events-none absolute left-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-gradient-to-r from-indigo-500 to-violet-500"
animate={{ width: `${pct}%` }}
transition={fillTransition}
/>
<input
type="range"
min={0}
max={STOPS.length - 1}
step={1}
value={index}
onChange={onSlide}
aria-label="Monthly API requests"
aria-valuetext={`${formatRequests(requests)} requests per month — ${tier.name} tier — ${formatMoney(effectiveMonthly)} per month`}
className="absolute inset-0 z-10 h-6 w-full cursor-pointer appearance-none bg-transparent outline-none [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:cursor-pointer [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-indigo-500 [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:shadow-md [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:cursor-pointer [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-indigo-500 [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:transition-transform hover:[&::-webkit-slider-thumb]:scale-110 focus-visible:[&::-webkit-slider-thumb]:ring-4 focus-visible:[&::-webkit-slider-thumb]:ring-indigo-500/30"
/>
</div>
<div className="mt-3 flex justify-between text-xs font-medium text-slate-400 dark:text-slate-500">
<span>25K</span>
<span>1M</span>
<span>50M</span>
</div>
</div>
<p className="mt-6 text-sm text-slate-500 dark:text-slate-400">
The first{" "}
<span className="font-semibold text-slate-700 dark:text-slate-200">
100K requests
</span>{" "}
are free, every month. Metering starts only after that.
</p>
{/* Features */}
<div className="mt-7 border-t border-slate-200/70 pt-6 dark:border-white/10">
<p className="text-xs font-semibold uppercase tracking-[0.12em] text-slate-400 dark:text-slate-500">
Included in {tier.name}
</p>
<ul className="mt-4 grid gap-x-6 gap-y-3 sm:grid-cols-2">
{tier.features.map((f) => (
<li
key={f}
className="flex items-start gap-2.5 text-sm text-slate-700 dark:text-slate-300"
>
<span className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-indigo-100 text-indigo-600 dark:bg-indigo-500/15 dark:text-indigo-300">
<CheckIcon />
</span>
{f}
</li>
))}
</ul>
</div>
</div>
{/* Right — price panel */}
<div className="relative bg-gradient-to-b from-white to-slate-50 p-6 sm:p-9 dark:from-slate-900/80 dark:to-slate-950/60">
<div
className="pxus-glow pointer-events-none absolute inset-x-6 top-0 h-px bg-gradient-to-r from-transparent via-indigo-400/60 to-transparent"
aria-hidden="true"
/>
<p className="text-sm font-medium text-slate-500 dark:text-slate-400">
Your estimated {billing === "annual" ? "monthly" : ""} price
</p>
<div className="mt-2 flex items-baseline gap-1">
<span className="text-5xl font-semibold tracking-tight tabular-nums sm:text-6xl">
{priceLabel}
</span>
<span className="text-lg font-normal text-slate-400 dark:text-slate-500">
/ mo
</span>
</div>
<p className="mt-2 text-sm text-slate-500 dark:text-slate-400">
{billing === "annual" ? (
<>
Billed{" "}
<span className="font-semibold text-slate-700 dark:text-slate-200">
{formatMoney(yearlyTotal)}/yr
</span>{" "}
· you save{" "}
<span className="font-semibold text-emerald-600 dark:text-emerald-400">
{formatMoney(yearlySaved)}
</span>
</>
) : (
<>Billed monthly · switch to annual to save 17%</>
)}
</p>
{/* Breakdown */}
<dl className="mt-6 space-y-2.5 border-t border-slate-200/70 pt-6 text-sm dark:border-white/10">
<div className="flex items-center justify-between">
<dt className="text-slate-500 dark:text-slate-400">
Platform · {tier.name}
</dt>
<dd className="font-medium tabular-nums text-slate-700 dark:text-slate-200">
{formatMoney(tier.base)}
</dd>
</div>
<div className="flex items-center justify-between">
<dt className="text-slate-500 dark:text-slate-400">
Metered requests
</dt>
<dd className="font-medium tabular-nums text-slate-700 dark:text-slate-200">
{usageCost === 0 ? "Free" : formatMoney(usageCost)}
</dd>
</div>
{billing === "annual" && (
<div className="flex items-center justify-between">
<dt className="text-slate-500 dark:text-slate-400">
Annual discount
</dt>
<dd className="font-medium tabular-nums text-emerald-600 dark:text-emerald-400">
-{formatMoney(monthly * ANNUAL_DISCOUNT)}
</dd>
</div>
)}
<div className="flex items-center justify-between border-t border-slate-200/70 pt-3 dark:border-white/10">
<dt className="font-semibold text-slate-800 dark:text-slate-100">
Total / mo
</dt>
<dd className="font-semibold tabular-nums text-slate-900 dark:text-white">
{formatMoney(effectiveMonthly)}
</dd>
</div>
</dl>
{/* CTAs */}
<div className="mt-7 space-y-3">
<button
type="button"
className="group flex w-full items-center justify-center gap-2 rounded-xl bg-indigo-600 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-indigo-600/25 outline-none transition-colors hover:bg-indigo-500 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-950"
>
{tier.key === "scale"
? "Talk to our team"
: "Start 14-day free trial"}
<span className="transition-transform group-hover:translate-x-0.5">
<ArrowIcon />
</span>
</button>
<button
type="button"
className="flex w-full items-center justify-center rounded-xl border border-slate-300 bg-white px-5 py-3 text-sm font-semibold text-slate-700 outline-none transition-colors hover:bg-slate-50 focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-white/15 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:focus-visible:ring-offset-slate-950"
>
Book a walkthrough
</button>
</div>
<p className="mt-5 text-center text-xs leading-relaxed text-slate-400 dark:text-slate-500">
No card required · Cancel anytime · SOC 2 Type II certified
</p>
</div>
</div>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Simple Pricing
OriginalA three-tier pricing section with a highlighted popular plan.

Single Plan Pricing Card
OriginalA focused one-plan pricing card with price, trial badge, feature list, call to action and a money-back trust note, ideal when you sell a single flat-rate product.

Three Tier Pricing With Monthly Annual Toggle
OriginalA three-tier pricing grid with a pure-CSS monthly and annual billing toggle that swaps every price with no JavaScript, using a native checkbox and Tailwind group-has state.

Plan Feature Comparison Table
OriginalA responsive, semantic feature comparison table across three plans with grouped rows, tick and dash indicators and screen-reader labels so visitors can weigh every feature side by side.

Staggered Glow Pricing Tiers
OriginalA three-tier pricing section whose cards reveal in a staggered spring cascade, with a rotating conic-gradient glow on the popular plan and prices that count up smoothly when you toggle monthly and annual billing.

Spotlight Cursor Pricing Cards
OriginalPricing cards that light up with a cursor-following radial spotlight, drift over floating background orbs and rise into view with a staggered spring entrance, plus a pulsing glow on the popular plan.

Highlight Follow Pricing Plans
OriginalA pricing row where an animated gradient highlight glides via shared layout animation to whichever plan you hover, a three-way billing switch slides its pill, prices recount instantly and a perks marquee scrolls beneath.

Three Tier Pricing
Originalthree-tier pricing cards

Toggle Annual Pricing
Originalpricing with monthly/annual toggle

Single Highlight Pricing
Originalsingle highlighted plan

Comparison Table Pricing
Originalpricing comparison table

Cards Gradient Pricing
Originalgradient pricing cards

