Docs
Live Pricing Calculator

Live Pricing Calculator

A transparent, interactive pricing calculator that builds trust. Features real-time cost estimation, sliders, and monthly/yearly savings toggles.

Transparent usage pricing

Pay only for what you use. No hidden fees.

5 users
150+
100 GB
10 GB1 TB
MonthlyYearly -20%

Estimated Cost

$123/month

Billed $1478 yearly

Base Platform$29
Users (5 × $15)$75
Storage (100GB × $0.5)$50
Yearly Discount (20%)-$370

Features

  • Interactive Sliders - Let users estimate costs based on their actual usage
  • Real-time Updates - Price updates instantly as inputs change
  • Annual Savings Toggle - Highlight the discount for yearly billing
  • Transparent Breakdown - Show exactly how the cost is calculated
  • Contact Sales Trigger - Logic to handle high-volume enterprise use cases
  • Smooth Animations - Numbers transition smoothly using framer-motion
  • Mobile Responsive - Works perfectly on all device sizes

Installation

Copy and paste the following code into your project.

"use client";
 
import * as React from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Check, HelpCircle, Info, Minus, Plus } from "lucide-react";
import { cn } from "@/lib/utils";
 
interface PricingTier {
  name: string;
  price: number;
  description: string;
  features: string[];
  highlight?: boolean;
}
 
interface PricingCalculatorProps {
  headline?: string;
  description?: string;
  currency?: string;
  basePrice?: number;
  plans?: PricingTier[];
  className?: string;
}
 
export function PricingCalculator({
  headline = "Estimate your costs",
  description = "Transparent pricing that scales with you.",
  currency = "$",
  basePrice = 0,
  className,
}: PricingCalculatorProps) {
  const [users, setUsers] = React.useState(5);
  const [storage, setStorage] = React.useState(100);
  const [isAnnual, setIsAnnual] = React.useState(true);
 
  // Pricing logic (example)
  const userPrice = 15;
  const storagePrice = 0.5;
 
  const monthlyTotal = basePrice + users * userPrice + storage * storagePrice;
  const annualTotal = monthlyTotal * 12 * 0.8; // 20% discount
  const displayedMonthly = isAnnual ? annualTotal / 12 : monthlyTotal;
 
  return (
    <section className={cn("w-full py-12 sm:py-16 lg:py-20", className)}>
      <div className="container px-4 md:px-6">
        <div className="mx-auto max-w-3xl space-y-4 text-center">
          <h2 className="text-3xl font-bold tracking-tighter text-foreground sm:text-4xl md:text-5xl">
            {headline}
          </h2>
          {description && (
            <p className="text-lg text-muted-foreground md:text-xl">
              {description}
            </p>
          )}
        </div>
 
        <div className="mx-auto mt-12 grid max-w-5xl gap-8 lg:grid-cols-2">
          {/* Controls */}
          <div className="space-y-8 rounded-2xl border border-border bg-card p-8 shadow-sm">
            <div className="space-y-6">
              {/* Users Slider */}
              <div className="space-y-4">
                <div className="flex items-center justify-between">
                  <label className="text-sm font-medium text-foreground">
                    Team Members
                  </label>
                  <span className="rounded-md bg-muted px-2 py-1 text-sm font-mono font-medium text-foreground">
                    {users} users
                  </span>
                </div>
                <input
                  type="range"
                  min="1"
                  max="50"
                  step="1"
                  value={users}
                  onChange={(e) => setUsers(Number(e.target.value))}
                  className="h-2 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary"
                />
                <div className="flex justify-between text-xs text-muted-foreground">
                  <span>1</span>
                  <span>50+</span>
                </div>
              </div>
 
              {/* Storage Slider */}
              <div className="space-y-4">
                <div className="flex items-center justify-between">
                  <label className="text-sm font-medium text-foreground">
                    Storage (GB)
                  </label>
                  <span className="rounded-md bg-muted px-2 py-1 text-sm font-mono font-medium text-foreground">
                    {storage} GB
                  </span>
                </div>
                <input
                  type="range"
                  min="10"
                  max="1000"
                  step="10"
                  value={storage}
                  onChange={(e) => setStorage(Number(e.target.value))}
                  className="h-2 w-full cursor-pointer appearance-none rounded-full bg-muted accent-primary"
                />
                <div className="flex justify-between text-xs text-muted-foreground">
                  <span>10 GB</span>
                  <span>1 TB</span>
                </div>
              </div>
 
              {/* Billing Toggle */}
              <div className="flex items-center justify-center space-x-4 pt-4">
                <span
                  className={cn(
                    "text-sm",
                    !isAnnual
                      ? "font-semibold text-foreground"
                      : "text-muted-foreground",
                  )}
                >
                  Monthly
                </span>
                <button
                  onClick={() => setIsAnnual(!isAnnual)}
                  className={cn(
                    "relative h-7 w-12 rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
                    isAnnual ? "bg-primary" : "bg-muted",
                  )}
                >
                  <motion.div
                    className="absolute top-1 h-5 w-5 rounded-full bg-white shadow-sm"
                    animate={{ left: isAnnual ? "26px" : "2px" }}
                    transition={{ type: "spring", stiffness: 500, damping: 30 }}
                  />
                </button>
                <span
                  className={cn(
                    "text-sm",
                    isAnnual
                      ? "font-semibold text-foreground"
                      : "text-muted-foreground",
                  )}
                >
                  Yearly{" "}
                  <span className="ml-1.5 rounded-full bg-green-500/10 px-2 py-0.5 text-xs font-medium text-green-600 dark:text-green-400">
                    -20%
                  </span>
                </span>
              </div>
            </div>
          </div>
 
          {/* Summary Card */}
          <div className="relative overflow-hidden rounded-2xl border border-primary/20 bg-primary/5 p-8">
            <div className="absolute -right-20 -top-20 h-64 w-64 rounded-full bg-primary/10 blur-3xl" />
            <div className="absolute -bottom-20 -left-20 h-64 w-64 rounded-full bg-purple-500/10 blur-3xl" />
 
            <div className="relative flex h-full flex-col justify-between space-y-8">
              <div>
                <h3 className="text-lg font-medium text-foreground">
                  Estimated Cost
                </h3>
                <div className="mt-4 flex items-baseline">
                  <span className="text-5xl font-bold tracking-tight text-foreground">
                    {currency}
                    <AnimatePresence mode="popLayout">
                      <motion.span
                        key={Math.round(displayedMonthly)}
                        initial={{ opacity: 0, y: 20 }}
                        animate={{ opacity: 1, y: 0 }}
                        exit={{ opacity: 0, y: -20 }}
                        transition={{ duration: 0.2 }}
                        className="inline-block"
                      >
                        {Math.round(displayedMonthly)}
                      </motion.span>
                    </AnimatePresence>
                  </span>
                  <span className="ml-2 text-muted-foreground">/month</span>
                </div>
                {isAnnual && (
                  <p className="mt-2 text-sm text-muted-foreground">
                    Billed {currency}
                    {Math.round(annualTotal)} yearly
                  </p>
                )}
              </div>
 
              <div className="space-y-4">
                <div className="flex justify-between border-b border-primary/10 pb-4 text-sm">
                  <span className="text-muted-foreground">Base Platform</span>
                  <span className="font-medium">
                    {currency}
                    {basePrice}
                  </span>
                </div>
                <div className="flex justify-between border-b border-primary/10 pb-4 text-sm">
                  <span className="text-muted-foreground">
                    Users ({users} × {currency}
                    {userPrice})
                  </span>
                  <span className="font-medium">
                    {currency}
                    {users * userPrice}
                  </span>
                </div>
                <div className="flex justify-between border-b border-primary/10 pb-4 text-sm">
                  <span className="text-muted-foreground">
                    Storage ({storage}GB × {currency}
                    {storagePrice})
                  </span>
                  <span className="font-medium">
                    {currency}
                    {storage * storagePrice}
                  </span>
                </div>
                {isAnnual && (
                  <div className="flex justify-between text-sm text-green-600 dark:text-green-400">
                    <span className="font-medium">Yearly Discount (20%)</span>
                    <span className="font-medium">
                      -{currency}
                      {Math.round(monthlyTotal * 12 - annualTotal)}
                    </span>
                  </div>
                )}
              </div>
 
              <button className="w-full rounded-lg bg-primary px-4 py-3 text-sm font-bold text-primary-foreground shadow-lg transition-transform hover:scale-[1.02] active:scale-[0.98]">
                Start Free Trial
              </button>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Update the import paths to match your project setup.

Usage

import { PricingCalculator } from "@/components/ui/pricing-calculator";
 
export default function Page() {
  return (
    <PricingCalculator
      headline="Estimate your costs"
      description="Pay only for what you use."
      basePrice={29}
      currency="$"
    />
  );
}

Props

PropTypeDescription
headlinestringSection headline
descriptionstringSection description
currencystringCurrency symbol (default: "$")
basePricenumberBase monthly price before usage costs
classNamestringAdditional CSS classes

Conversion Psychology

Why this converts

  1. Transparency Builds Trust: Hiding pricing behind "Contact Sales" often leads to drop-offs. A calculator shows you have nothing to hide.
  2. Ownership Bias: By interacting with the sliders, users start to "build" their own plan, creating a sense of ownership before they even sign up.
  3. Anchoring: The "Yearly" toggle usually shows a lower monthly equivalent, anchoring the user to the savings rather than the total cost.
  4. Reduced Friction: Users get an immediate answer to "how much will this cost me?" without needing to do mental math or talk to a human.