Skip to main content
Glama
pricing-page.md13.2 kB
# Pricing Page Patterns ## Overview Common pricing page layouts for SaaS products, with tier comparisons, toggles, and feature lists. ## Pattern A: Card-Based Pricing with Toggle ### Characteristics - 3-tier pricing (Starter, Pro, Enterprise) - Monthly/Annually toggle with discount indicator - Featured tier with different styling - Feature list with checkmarks - Animated price transitions - **Common in**: B2B SaaS, subscription products ### Structure ```tsx 'use client' import { useState } from 'react' import { RadioGroup } from '@headlessui/react' import { CheckIcon } from '@heroicons/react/24/solid' import { motion } from 'framer-motion' const frequencies = [ { value: 'monthly', label: 'Monthly', priceSuffix: '/month' }, { value: 'annually', label: 'Annually', priceSuffix: '/month', discount: '20% off' }, ] const tiers = [ { name: 'Starter', id: 'tier-starter', href: '#', price: { monthly: '$29', annually: '$23' }, description: 'Perfect for small teams getting started.', features: [ 'Up to 10 team members', '10GB storage', 'Basic support', 'Mobile apps', ], featured: false, }, { name: 'Pro', id: 'tier-pro', href: '#', price: { monthly: '$99', annually: '$79' }, description: 'For growing teams with advanced needs.', features: [ 'Up to 50 team members', '100GB storage', 'Priority support', 'Mobile apps', 'Advanced analytics', 'Custom integrations', ], featured: true, }, { name: 'Enterprise', id: 'tier-enterprise', href: '#', price: { monthly: 'Custom', annually: 'Custom' }, description: 'Dedicated support and infrastructure.', features: [ 'Unlimited team members', 'Unlimited storage', '24/7 phone support', 'Mobile apps', 'Advanced analytics', 'Custom integrations', 'SSO', 'Dedicated account manager', ], featured: false, }, ] export function PricingCards() { const [frequency, setFrequency] = useState(frequencies[0]) return ( <div className="py-24 sm:py-32"> <div className="mx-auto max-w-7xl px-6 lg:px-8"> <div className="mx-auto max-w-4xl text-center"> <h2 className="text-base font-semibold leading-7 text-indigo-600">Pricing</h2> <p className="mt-2 text-4xl font-bold tracking-tight text-gray-900 sm:text-5xl"> Choose the right plan for you </p> </div> {/* Frequency Toggle */} <div className="mt-16 flex justify-center"> <RadioGroup value={frequency} onChange={setFrequency} className="grid grid-cols-2 gap-x-1 rounded-full bg-gray-100 p-1 text-center text-xs font-semibold leading-5" > {frequencies.map((option) => ( <RadioGroup.Option key={option.value} value={option} className={({ checked }) => `cursor-pointer rounded-full px-2.5 py-1 ${ checked ? 'bg-white text-gray-900 shadow' : 'text-gray-500' }` } > {option.label} </RadioGroup.Option> ))} </RadioGroup> </div> {/* Pricing Cards */} <div className="isolate mx-auto mt-10 grid max-w-md grid-cols-1 gap-8 lg:mx-0 lg:max-w-none lg:grid-cols-3"> {tiers.map((tier) => ( <div key={tier.id} className={`rounded-3xl p-8 ring-1 ${ tier.featured ? 'bg-indigo-600 ring-indigo-600' : 'bg-white ring-gray-200' }`} > <h3 className={`text-lg font-semibold leading-8 ${ tier.featured ? 'text-white' : 'text-gray-900' }`} > {tier.name} </h3> <p className={`mt-4 text-sm leading-6 ${ tier.featured ? 'text-indigo-100' : 'text-gray-600' }`} > {tier.description} </p> <p className="mt-6 flex items-baseline gap-x-1"> <motion.span key={frequency.value} initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} className={`text-4xl font-bold tracking-tight ${ tier.featured ? 'text-white' : 'text-gray-900' }`} > {tier.price[frequency.value as 'monthly' | 'annually']} </motion.span> {tier.price.monthly !== 'Custom' && ( <span className={`text-sm font-semibold leading-6 ${ tier.featured ? 'text-indigo-100' : 'text-gray-600' }`} > {frequency.priceSuffix} </span> )} </p> <a href={tier.href} className={`mt-6 block rounded-md px-3 py-2 text-center text-sm font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 ${ tier.featured ? 'bg-white text-indigo-600 hover:bg-indigo-50 focus-visible:outline-white' : 'bg-indigo-600 text-white hover:bg-indigo-500 focus-visible:outline-indigo-600' }`} > Get started </a> <ul role="list" className={`mt-8 space-y-3 text-sm leading-6 ${ tier.featured ? 'text-indigo-100' : 'text-gray-600' }`} > {tier.features.map((feature) => ( <li key={feature} className="flex gap-x-3"> <CheckIcon className={`h-6 w-5 flex-none ${ tier.featured ? 'text-white' : 'text-indigo-600' }`} aria-hidden="true" /> {feature} </li> ))} </ul> </div> ))} </div> </div> </div> ) } ``` ### Toggle Implementation with Animated Selector ```tsx <div className="relative"> <RadioGroup value={frequency} onChange={setFrequency} className="grid grid-cols-2"> <RadioGroup.Option value="monthly"> {({ checked }) => ( <span className={checked ? 'text-gray-900' : 'text-gray-500'}>Monthly</span> )} </RadioGroup.Option> <RadioGroup.Option value="annually"> {({ checked }) => ( <span className={checked ? 'text-gray-900' : 'text-gray-500'}>Annually</span> )} </RadioGroup.Option> </RadioGroup> {/* Animated background selector */} <motion.div layout className="absolute inset-y-0 left-0 w-1/2 rounded-full bg-white shadow" animate={{ x: frequency === 'annually' ? '100%' : '0%' }} /> </div> ``` ## Pattern B: Pricing with Comparison Table ### Characteristics - Initial pricing cards - Detailed comparison table below - Mobile dropdown to select tier for comparison - Feature categories (Features, Analysis, Support) - Check/minus icons for included features - **Common in**: Enterprise SaaS, complex products ### Structure ```tsx <div className="py-24"> {/* Pricing Cards (same as Pattern A) */} <PricingCards /> {/* Comparison Table */} <div className="mt-24"> <h3 className="text-2xl font-bold">Compare plans</h3> {/* Mobile: Dropdown Selector */} <div className="lg:hidden"> <Menu> <MenuButton>Select a plan to compare</MenuButton> <MenuItems> {tiers.map((tier) => ( <MenuItem key={tier.id}>{tier.name}</MenuItem> ))} </MenuItems> </Menu> </div> {/* Desktop: Full Table */} <table className="hidden lg:table w-full"> <caption className="sr-only">Feature comparison</caption> <colgroup> <col className="w-3/5" /> <col className="w-1/5" /> <col className="w-1/5" /> <col className="w-1/5" /> </colgroup> <thead> <tr> <th scope="col">Features</th> {tiers.map((tier) => ( <th key={tier.id} scope="col">{tier.name}</th> ))} </tr> </thead> <tbody> {featureCategories.map((category) => ( <> <tr key={category.name}> <th colSpan={4} scope="colgroup" className="bg-gray-50 py-3 pl-6 text-left text-sm font-semibold" > {category.name} </th> </tr> {category.features.map((feature) => ( <tr key={feature.name}> <th scope="row" className="py-4 pl-6 text-left text-sm font-normal"> {feature.name} </th> {tiers.map((tier) => ( <td key={tier.id} className="px-6 py-4 text-center"> {feature.tiers[tier.name] ? ( <CheckIcon className="mx-auto h-5 w-5 text-indigo-600" /> ) : ( <span className="text-gray-400">−</span> )} </td> ))} </tr> ))} </> ))} </tbody> </table> </div> </div> ``` ## Pattern C: Simple Inline Pricing ### Characteristics - Pricing integrated into single-page layout - Simple tier cards without complex features - Part of longer landing page flow - Minimal comparison - **Common in**: Simple products, one-product sites ### Structure ```tsx <section id="pricing" className="py-20"> <div className="mx-auto max-w-7xl px-6"> <h2 className="text-center text-3xl font-bold">Simple, transparent pricing</h2> <div className="mt-12 grid gap-8 md:grid-cols-3"> {simpleTiers.map((tier) => ( <div key={tier.name} className="rounded-lg border border-gray-200 p-8"> <h3 className="text-lg font-semibold">{tier.name}</h3> <p className="mt-4 text-4xl font-bold">{tier.price}</p> <p className="mt-2 text-sm text-gray-600">{tier.description}</p> <Button href={tier.href} className="mt-8 w-full"> Choose {tier.name} </Button> <ul className="mt-8 space-y-3"> {tier.features.map((feature) => ( <li key={feature} className="flex items-center text-sm"> <CheckIcon className="mr-3 h-5 w-5 text-green-500" /> {feature} </li> ))} </ul> </div> ))} </div> </div> </section> ``` ## Common Elements ### Discount Badge ```tsx {frequency.value === 'annually' && ( <span className="inline-flex items-center rounded-full bg-green-100 px-3 py-0.5 text-sm font-semibold text-green-800"> Save 20% </span> )} ``` ### Most Popular Badge ```tsx {tier.featured && ( <span className="absolute -top-5 left-0 right-0 mx-auto w-max rounded-full bg-indigo-600 px-3 py-1 text-sm font-semibold text-white"> Most popular </span> )} ``` ### Custom Enterprise CTA ```tsx {tier.name === 'Enterprise' && ( <Button href="/contact" variant="outline"> Contact sales </Button> )} ``` ## FAQ Section Often paired with pricing: ```tsx <div className="mt-24"> <h3 className="text-2xl font-bold text-center">Frequently asked questions</h3> <dl className="mt-12 space-y-6 divide-y divide-gray-200"> {faqs.map((faq) => ( <Disclosure as="div" key={faq.question} className="pt-6"> {({ open }) => ( <> <dt> <Disclosure.Button className="flex w-full items-start justify-between text-left"> <span className="text-base font-semibold">{faq.question}</span> <ChevronDownIcon className={`h-6 w-6 ${open ? 'rotate-180' : ''}`} /> </Disclosure.Button> </dt> <Disclosure.Panel as="dd" className="mt-2 pr-12"> <p className="text-base text-gray-600">{faq.answer}</p> </Disclosure.Panel> </> )} </Disclosure> ))} </dl> </div> ``` ## Accessibility Considerations - **Semantics**: Use proper table structure with caption, colgroup, thead, tbody - **Labels**: RadioGroup must have accessible labels - **Keyboard**: Ensure all interactive elements are keyboard accessible - **Screen Readers**: Use `aria-label` for icon-only indicators - **Focus**: Maintain visible focus indicators ## Animation Best Practices - **Price Changes**: Fade or slide when toggling monthly/annual - **Reduced Motion**: Respect prefers-reduced-motion - **Performance**: Use transform and opacity for animations - **Smooth**: Spring animations for natural feel ## Dependencies - `@headlessui/react` - RadioGroup, Disclosure, Menu - `@heroicons/react` - CheckIcon, ChevronDownIcon - `framer-motion` - Price transition animations

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CaullenOmdahl/Nextjs-React-Tailwind-Assistant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server