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 | number✅—Pricing 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() => void✅undefinedFunction 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