Pricing Card

A customizable pricing card component with smooth CSS-based hover animations.
Supports plan labels, badges, prices, descriptions, and call-to-action buttons, making it ideal for subscription tiers, product offers, or feature showcases.

Silver

SAVE 30%
70

PER MONTH

Serious opportunities for serious investors.\nThe choice is yours.

Billed as $299 yearly


Install dependencies

npm i clsx tailwind-merge framer-motion

Add Utility

lib/utils.ts

import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Code

PricingCard.tsx


"use client";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";

interface CardProps {
 planType?: string;
 badgeText?: string;
 price: string | number;
 duration?: string;
 description?: string;
 btnText?: string;
 bottomText?: string;
 className?: string;
 onClick?: () => void;
 animate?: boolean; 
}

export default function PricingCard({
 planType,
 badgeText,
 price,
 duration,
 description,
 btnText,
 bottomText,
 onClick,
 className,
 animate = true, // default to true
}: CardProps) {
 const Wrapper = animate ? motion.div : "div";

 return (
   <Wrapper
     {...(animate
       ? {
           initial: { scale: 0.9 },
           whileHover: { scale: 1 },
           transition: { duration: 0.3 },
         }
       : {})}
     className={cn(
       `max-w-3xs w-full max-h-fit text-white rounded-2xl [background:radial-gradient(circle_at_top,_#172554,#020617)] relative`,
       className
     )}
   >
     <div className="w-full h-full rounded-2xl border-l-[#312e81] border-l-2  border-r-[#312e81] border-r-2 relative hover:[box-shadow:inset_5px_5px_10px_rgba(59,_130,_246,_0.3),inset_-5px_-5px_10px_rgba(59,_130,_246,_0.3)]  flex flex-col items-center justify-center gap-1 p-4 sm:p-0">
       {/* header */}
       <div className="flex items-baseline justify-center gap-3 sm:gap-4 flex-wrap ">
         {planType && (
           <h4 className="text-xs sm:text-sm font-medium text-zinc-400 tracking-wider ">
             {planType}
           </h4>
         )}

         {badgeText && (
           <div className="p-[2px] rounded-3xl bg-[radial-gradient(circle_at_left,#4b5563_0%,#09090b_100%)] w-fit">
 <div className="px-5 py-2 rounded-3xl [background:radial-gradient(circle_at_right,#4b5563_0%,#09090b_100%)] text-[10px] sm:text-xs text-zinc-300 leading-none">
  {badgeText}
 </div>
</div>

         )}
       </div>

       {/* mid */}
       <div className="flex flex-col items-center justify-center ">
         {/* price */}
         <div className="flex items-center justify-center relative ">
           <div className="absolute top-2 -left-7 ">
             <svg
               xmlns="http://www.w3.org/2000/svg"
               width="28"
               height="28"
               viewBox="0 0 24 24"
               fill="none"
               stroke="#a1a1aa"
               strokeWidth="2"
               strokeLinecap="round"
               strokeLinejoin="round"
               className="lucide lucide-dollar-sign-icon lucide-dollar-sign"
             >
               <line x1="12" x2="12" y1="2" y2="22" />
               <path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
             </svg>
           </div>

           <div className="text-6xl sm:text-8xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-[#f3f4f6] to-[#52525b]">
             {price}
           </div>
         </div>

         <h2 className="text-[10px] sm:text-xs text-zinc-400 font-semibold tracking-wider">
           {duration}
         </h2>
         <p className="text-center text-balance text-zinc-400 text-xs sm:text-sm ">
           {description}
         </p>
       </div>

       {/* Bottom */}
       <div className="flex flex-col items-center justify-center">
         <button
           className="bg-zinc-200  rounded-xl  px-8 sm:px-12 py-2 text-xs  text-zinc-800 flex items-center justify-center font-semibold gap-2 max-w-fit shadow-sm shadow-zinc-400"
           onClick={onClick}
         >
           {btnText}
           <svg
             xmlns="http://www.w3.org/2000/svg"
             width="18"
             height="18"
             viewBox="0 0 24 24"
             fill="none"
             stroke="currentColor"
             strokeWidth="2"
             strokeLinecap="round"
             strokeLinejoin="round"
             className="lucide lucide-move-right-icon lucide-move-right"
           >
             <path d="M18 8L22 12L18 16" />
             <path d="M2 12H22" />
           </svg>
         </button>
         <p className="text-center text-balance  text-zinc-400  text-[10px] sm:text-xs px-2 mt-2 tracking-wide">
           {bottomText}
         </p>
       </div>
     </div>
   </Wrapper>
 );
}

Props

PropTypeRequiredDefaultDescription
pricestring | numberPricing value to be displayed prominently.
planTypestring""Label for the pricing plan (e.g., "Starter", "Pro").
badgeTextstring""Badge like "Popular" or "Best Value".
durationstring""Time interval for the pricing (e.g., "per month").
descriptionstring""Short explanation or offer under the price.
btnTextstring""Button text (e.g., "Get Started").
bottomTextstring""Informational text below the CTA button.
onClick() => voidundefinedFunction triggered when the CTA button is clicked.
classNamestring🟡""Additional Tailwind classes to apply to the outer wrapper.
animateboolean🟡trueEnables hover scale animation using Framer Motion.
Pricing Card16