Web InnoventixFreeCode

Usage Slider Pricing

Original · free

usage-based pricing with a slider

byWeb InnoventixReact + Tailwind
pricexusagesliderpricing
Open

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
pricex-usage-slider.tsx
"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

motion

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 quote

Similar components

Browse all →
Simple Pricing

Simple Pricing

Original

A three-tier pricing section with a highlighted popular plan.

Single Plan Pricing Card

Single Plan Pricing Card

Original

A 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

Three Tier Pricing With Monthly Annual Toggle

Original

A 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

Plan Feature Comparison Table

Original

A 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

Staggered Glow Pricing Tiers

Original

A 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

Spotlight Cursor Pricing Cards

Original

Pricing 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

Highlight Follow Pricing Plans

Original

A 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

Three Tier Pricing

Original

three-tier pricing cards

Toggle Annual Pricing

Toggle Annual Pricing

Original

pricing with monthly/annual toggle

Single Highlight Pricing

Single Highlight Pricing

Original

single highlighted plan

Comparison Table Pricing

Comparison Table Pricing

Original

pricing comparison table

Cards Gradient Pricing

Cards Gradient Pricing

Original

gradient pricing cards