Pricing Components
The pricing module provides a flexible, composable pricing display system with support for monthly/annual billing cycles and plan highlighting.
Components Overview
PricingGrid Responsive grid layout for displaying multiple pricing plans
BillingToggle Monthly/Annual billing cycle switcher
PricingCard Individual plan card with features and CTA
PricingGrid
A responsive grid container that displays pricing plans in a 3-column layout on desktop, adapting to single column on mobile.
Props
plans
(Plan & { currentPrice: number })[]
required
Array of plan objects with pricing information. Each plan includes base Plan properties plus a calculated currentPrice based on billing cycle.
Plan Type Structure
modules/prices/types/plan.ts
export interface Plan {
id : string ;
name : string ;
description : string ;
priceMonth : number ;
priceYear : number ;
features : string [];
buttonText : string ;
highlight : boolean ;
}
Usage
modules/prices/screens/pricing-screen.tsx
import { PricingGrid } from "../components/pricing-grid" ;
import { usePricingPlans } from "../hooks/usePrincingPlans" ;
export function PricingScreen ({ initialPlans } : { initialPlans : Plan [] }) {
const { plans } = usePricingPlans ( initialPlans );
return (
< div className = "flex flex-col items-center" >
< PricingGrid plans = { plans } />
</ div >
);
}
Layout
The grid uses responsive Tailwind classes:
Mobile : Single column (grid-cols-1)
Tablet/Desktop : Three columns (md:grid-cols-3)
Gap : gap-8 between cards
Width : max-w-6xl centered container
< div className = "grid grid-cols-1 md:grid-cols-3 gap-8 w-full max-w-6xl" >
{ plans . map (( plan ) => (
< PricingCard key = { plan . id } plan = { plan } displayPrice = { plan . currentPrice } />
)) }
</ div >
BillingToggle
Toggle switch for switching between monthly and annual billing cycles, with a visual discount indicator for annual plans.
Props
Current billing cycle state. true for annual, false for monthly.
Callback function triggered when the toggle is clicked. Should update the billing cycle state.
Usage
modules/prices/screens/pricing-screen.tsx
import { BillingToggle } from "../components/billing-toggle" ;
import { usePricingPlans } from "../hooks/usePrincingPlans" ;
export function PricingScreen ({ initialPlans }) {
const { isAnnual , toggleBilling } = usePricingPlans ( initialPlans );
return (
< div className = "flex flex-col items-center" >
< BillingToggle isAnnual = { isAnnual } onToggle = { toggleBilling } />
</ div >
);
}
Visual Design
Toggle Background : Dark navy bg-[#121f3d]
Toggle Knob : White circle that slides between positions
Active State : Bold text for selected option
Inactive State : text-gray-500 for unselected option
Discount Badge : Green “-20%” indicator on annual option
Component Structure
modules/prices/components/billing-toggle.tsx
< div className = "flex items-center gap-4 mb-12" >
< span className = { `text-sm ${ ! isAnnual ? "font-bold" : "text-gray-500" } ` } >
Mensual
</ span >
< button onClick = { onToggle } className = "relative w-12 h-6 bg-[#121f3d] rounded-full p-1" >
< div className = { `w-4 h-4 bg-white rounded-full transition-all ${
isAnnual ? "translate-x-6" : "translate-x-0"
} ` } />
</ button >
< span className = { `text-sm ${ isAnnual ? "font-bold" : "text-gray-500" } ` } >
Anual < span className = "text-green-500 ml-1 text-xs font-bold" > -20% </ span >
</ span >
</ div >
Animation
The toggle uses Tailwind’s transition-all for smooth animation:
Knob slides with translate-x-6 when annual is selected
Text weight changes instantly
Color transitions are smooth
PricingCard
Individual pricing plan card component with features list, pricing display, and call-to-action button.
Props
The plan object containing all plan details (name, description, features, etc.)
The calculated price to display based on current billing cycle. Passed separately to allow dynamic pricing without mutating the plan object.
Usage
import { PricingCard } from "../components/pricing-card" ;
const plan = {
id: "pro" ,
name: "Pro" ,
description: "Para empresas en crecimiento" ,
priceMonth: 49 ,
priceYear: 39 ,
features: [
"5,000 conversaciones/mes" ,
"Integración con CRM" ,
"Analytics avanzado" ,
"Soporte prioritario" ,
],
buttonText: "Comenzar prueba" ,
highlight: true ,
};
< PricingCard plan = { plan } displayPrice = { 39 } /> ;
Conditional Styling
The card adapts its appearance based on the highlight prop:
Highlighted Plan
Standard Plan
// Styling for highlighted plans (e.g., "Most Popular")
className = "bg-[#121f3d] text-white scale-105 shadow-xl z-10"
Dark navy background
White text
Slightly larger scale (105%)
Elevated z-index
“Más elegido” badge at top
White CTA button with dark text
// Styling for standard plans
className = "bg-white text-gray-900 shadow-sm border-gray-100"
White background
Dark text
Subtle shadow
Light border
Blue-tinted CTA button
Card Structure
modules/prices/components/pricing-card.tsx
< div className = { /* conditional styling */ } >
{ /* Optional "Más elegido" badge */ }
{ highlight && (
< span className = "text-[10px] uppercase tracking-widest font-bold text-blue-400" >
Más elegido
</ span >
) }
{ /* Plan name and description */ }
< h3 className = "text-xl font-bold" > { name } </ h3 >
< p className = "text-sm mb-6" > { description } </ p >
{ /* Price display */ }
< div className = "flex items-baseline mb-8" >
< span className = "text-sm font-medium" > USD </ span >
< span className = "text-5xl font-black mx-1" > { displayPrice } </ span >
< span className = "text-sm" > / mes </ span >
</ div >
{ /* Features list */ }
< ul className = "space-y-4 mb-8" >
{ features . map (( feature ) => (
< li key = { feature } className = "flex items-start text-sm" >
< span className = "mr-3 text-blue-500 mt-0.5" > ✓ </ span >
< span > { feature } </ span >
</ li >
)) }
</ ul >
{ /* CTA button */ }
< button className = { /* conditional styling */ } >
{ buttonText }
</ button >
</ div >
Typography
Plan Name : text-xl font-bold
Description : text-sm with muted color
Price : text-5xl font-black for emphasis
Currency : text-sm font-medium USD prefix
Features : text-sm with checkmark icons
Complete Integration Example
PricingScreen.tsx
usePricingPlans.ts (Hook)
plan.ts (Type Definition)
import { Plan } from "../types/plan" ;
import { BillingToggle } from "../components/billing-toggle" ;
import { PricingGrid } from "../components/pricing-grid" ;
import { usePricingPlans } from "../hooks/usePrincingPlans" ;
interface PricingScreenProps {
initialPlans : Plan [];
}
export function PricingScreen ({ initialPlans } : PricingScreenProps ) {
const { plans , isAnnual , toggleBilling } = usePricingPlans ( initialPlans );
return (
< main className = "min-h-screen bg-gray-50 py-20 px-4" >
{ /* Header */ }
< section className = "max-w-4xl mx-auto text-center mb-16" >
< h2 className = "text-blue-600 font-bold uppercase tracking-widest text-sm mb-4" >
Planes
</ h2 >
< h1 className = "text-4xl md:text-5xl font-black text-[#121f3d] mb-6" >
Precios simples y transparentes
</ h1 >
< p className = "text-gray-500 text-lg" >
Comenzá con 7 días gratuitos. Sin tarjeta de crédito.
</ p >
</ section >
{ /* Pricing Components */ }
< div className = "flex flex-col items-center" >
< BillingToggle isAnnual = { isAnnual } onToggle = { toggleBilling } />
< PricingGrid plans = { plans } />
</ div >
</ main >
);
}
Component Composition
The pricing components follow a composable architecture:
Data Flow:
PricingScreen receives initial plans as props
usePricingPlans hook manages billing cycle state
Hook calculates currentPrice for each plan based on cycle
BillingToggle controls the billing state
PricingGrid receives enriched plans array
Each PricingCard renders with calculated price
Customization Examples
Add Custom Discount Badge
Modify the BillingToggle to show different discount percentages: < span className = "text-green-500 ml-1 text-xs font-bold" >
- { discountPercentage } %
</ span >
Customize Card Highlighting
Change the highlighted card styling: // Use gradient instead of solid background
className = "bg-gradient-to-br from-blue-600 to-blue-800 text-white"
Replace checkmarks with custom icons: import { Check } from "lucide-react" ;
< li className = "flex items-start" >
< Check className = "mr-3 text-blue-500 h-5 w-5" />
< span > { feature } </ span >
</ li >
Best Practices
Price Calculation : Always calculate currentPrice based on billing cycle to avoid showing incorrect prices. Use the usePricingPlans hook pattern.
Highlight One Plan : Set highlight: true for only one plan (typically the most popular) to create visual hierarchy and guide user choice.
Responsive Testing : Test the 3-column layout on various screen sizes. On mobile, cards stack vertically and the scale effect on highlighted cards may cause overflow.
usePricingPlans Hook Hook for managing pricing state and calculations
Plan Type TypeScript interface for plan objects
Pricing Service API service for fetching plans from backend
Payment Integration Connect pricing to checkout flow