Interactive Pricing
A pricing section with morphing numbers and a premium toggle.
Installation
Add this component to your project using the CLI:
terminal
npx vui-registry-cli-v1 add pricingSource Code
pricing.tsx
'use client'
import React, { useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { cn } from '@/lib/utils'
interface PricingPlan {
id: string
name: string
price: {
monthly: number
yearly: number
}
description: string
features: string[]
popular?: boolean
}
const CheckIcon = () => (
<svg className="w-3 h-3 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={4}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
</svg>
)
export interface PricingProps {
plans?: PricingPlan[]
defaultBillingCycle?: 'monthly' | 'yearly'
className?: string
}
export function Pricing({ plans: plansProp, defaultBillingCycle = 'monthly', className }: PricingProps) {
const [isYearly, setIsYearly] = useState(defaultBillingCycle === 'yearly')
const plans: PricingPlan[] = plansProp ?? [
{
id: 'starter',
name: 'Starter',
price: { monthly: 15, yearly: 12 },
description: 'Perfect for side projects',
features: ['5 Projects', 'Basic Analytics', 'Community Support', '1GB Storage'],
},
{
id: 'pro',
name: 'Pro',
price: { monthly: 39, yearly: 32 },
description: 'For growing businesses',
features: ['Unlimited Projects', 'Advanced Analytics', 'Priority Support', 'Team Access', '10GB Storage', 'Custom Domain'],
popular: true,
},
{
id: 'enterprise',
name: 'Enterprise',
price: { monthly: 99, yearly: 89 },
description: 'For large scale apps',
features: ['Unlimited Everything', 'Custom SLA', 'Dedicated Manager', 'SSO', 'Unlimited Storage', 'On-premise Deployment'],
},
]
return (
<div className={cn("w-full max-w-6xl mx-auto px-4 py-12 flex flex-col items-center", className)}>
{/* Toggle Switch */}
<div className="flex items-center gap-4 mb-16 bg-muted/30 p-1.5 rounded-full border border-border/50 backdrop-blur-sm">
<span className={cn('px-3 py-1 text-sm font-medium transition-colors cursor-pointer', !isYearly ? 'text-foreground' : 'text-muted-foreground')} onClick={() => setIsYearly(false)}>
Monthly
</span>
<button
onClick={() => setIsYearly(!isYearly)}
className="relative w-14 h-8 rounded-full bg-muted border border-border p-1 transition-colors hover:border-primary/50 focus:outline-none focus:ring-2 focus:ring-primary/20"
>
<motion.div
className="w-6 h-6 rounded-full bg-primary shadow-sm"
animate={{ x: isYearly ? 24 : 0 }}
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
/>
</button>
<span className={cn('px-3 py-1 text-sm font-medium transition-colors cursor-pointer flex items-center gap-2', isYearly ? 'text-foreground' : 'text-muted-foreground')} onClick={() => setIsYearly(true)}>
Yearly <span className="text-[10px] font-bold text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20">-20%</span>
</span>
</div>
{/* Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 w-full items-start">
{plans.map((plan) => (
<PricingCard key={plan.id} plan={plan} isYearly={isYearly} />
))}
</div>
</div>
)
}
function PricingCard({ plan, isYearly }: { plan: PricingPlan; isYearly: boolean }) {
return (
<div
className={cn(
'relative flex flex-col p-6 rounded-3xl transition-all duration-300 group h-full',
plan.popular
? 'bg-[#121212] shadow-2xl shadow-black/50 z-10 scale-105 border border-white/10'
: 'bg-black/20 border border-white/5 hover:border-white/10 shadow-sm hover:shadow-xl hover:-translate-y-1 backdrop-blur-sm'
)}
>
{/* Double Border Effect */}
<div className={cn(
"absolute inset-0 rounded-3xl pointer-events-none transition-opacity duration-500 border-2",
plan.popular ? "border-white/5" : "border-transparent group-hover:border-white/5"
)} />
{plan.popular && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2 px-3 py-1 rounded-full bg-white text-black text-[10px] font-bold shadow-[0_0_20px_rgba(255,255,255,0.3)] tracking-widest uppercase border border-white/20">
Most Popular
</div>
)}
{/* Hover Spotlight/Glow Effect */}
<div className="absolute inset-0 rounded-3xl opacity-0 group-hover:opacity-100 transition-opacity duration-500 pointer-events-none bg-[radial-gradient(600px_circle_at_var(--mouse-x,50%)_var(--mouse-y,0%),rgba(255,255,255,0.03),transparent_40%)]" />
<div className="mb-4 relative z-10">
<h3 className={cn("text-lg font-medium tracking-wide", plan.popular ? "text-white" : "text-white/70")}>{plan.name}</h3>
<p className="text-xs text-white/40 mt-1 font-medium leading-relaxed">{plan.description}</p>
</div>
<div className="mb-6 flex items-baseline gap-1 relative z-10">
<span className="text-xl font-bold text-white/20">$</span>
<div className="h-10 relative flex items-center">
<AnimatePresence mode="popLayout">
<motion.span
key={isYearly ? 'year' : 'month'}
initial={{ y: 15, opacity: 0, filter: 'blur(4px)' }}
animate={{ y: 0, opacity: 1, filter: 'blur(0px)' }}
exit={{ y: -15, opacity: 0, filter: 'blur(4px)' }}
transition={{ type: 'spring', stiffness: 200, damping: 20 }}
className="text-4xl font-extrabold text-white tracking-tight leading-none"
>
{isYearly ? plan.price.yearly : plan.price.monthly}
</motion.span>
</AnimatePresence>
</div>
<span className="text-white/30 text-sm font-medium ml-1">/mo</span>
</div>
<div className="flex-1 space-y-3 mb-8 relative z-10">
{plan.features.map((feature) => (
<div key={feature} className="flex items-center gap-3 text-sm group/item">
<div className={cn(
"w-4 h-4 rounded-full flex items-center justify-center shrink-0 transition-all duration-300",
plan.popular
? "bg-white/10 text-white shadow-[0_0_10px_rgba(255,255,255,0.1)]"
: "bg-white/5 text-white/30 group-hover/item:bg-white/10 group-hover/item:text-white group-hover/item:scale-110"
)}>
<CheckIcon />
</div>
<span className="text-white/50 group-hover/item:text-white/80 transition-colors duration-300 text-xs">{feature}</span>
</div>
))}
</div>
<button
className={cn(
'w-full py-3 rounded-xl font-semibold text-xs transition-all duration-300 relative overflow-hidden z-10 border tracking-wide uppercase',
plan.popular
? 'bg-white text-black border-transparent hover:shadow-[0_0_20px_rgba(255,255,255,0.2)] hover:scale-[1.02]'
: 'bg-transparent border-white/10 text-white/70 hover:bg-white/5 hover:text-white hover:border-white/20'
)}
>
Get Started
</button>
</div>
)
}
Dependencies
framer-motion: latestlucide-react: latestclsx: latesttailwind-merge: latest
Props
Component property reference.
| Name | Type | Default | Description |
|---|---|---|---|
| plans | Array<{ id: string; name: string; price: { monthly: number; yearly: number }; description: string; features: string[]; popular?: boolean }> | Built-in plans | Custom pricing plans to display. |
| defaultBillingCycle | 'monthly' | 'yearly' | 'monthly' | Initial billing cycle. |
| className | string | undefined | Additional CSS classes. |
Context Worth Keeping In Orbit
Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.
I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.
I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.

