Pricing

Pricing 01 — Three Tiers

Three pricing cards with a highlighted middle tier, primary glow, token-tinted check rows and staggered reveals.

$ npx shadcn@latest add @ludusvibe/pricing-01
Open in v0

Code

Installed to components/sections/pricing-01.tsx — it's yours to edit.

"use client"

import * as React from "react"
import { motion } from "motion/react"
import { Check } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import { buttonVariants } from "@/components/ui/button"
import { Reveal } from "@/components/ludus/reveal"
import { fadeUp, stagger, viewport } from "@/lib/motion"
import { cn } from "@/lib/utils"

type Cta = { label: string; href: string }

export type Pricing01Tier = {
  name: string
  price: string
  /** Suffix rendered after the price, e.g. "/month". Omit for custom pricing. */
  period?: string
  description: string
  features: string[]
  cta: Cta
  /** Visually emphasizes this tier with a badge, primary border and glow. */
  highlighted?: boolean
}

export type Pricing01Props = {
  eyebrow?: string
  title?: React.ReactNode
  description?: string
  /** Badge label shown on the highlighted tier. */
  highlightLabel?: string
  tiers?: Pricing01Tier[]
  /** Heading element — use "h1" when this is the page's main heading. */
  as?: "h1" | "h2"
  className?: string
}

const defaultTiers: Pricing01Tier[] = [
  {
    name: "Starter",
    price: "$19",
    period: "/month",
    description: "For solo builders shipping their first product.",
    features: [
      "Up to 3 projects",
      "10k monthly page views",
      "Core analytics dashboard",
      "Email capture forms",
      "Community support",
    ],
    cta: { label: "Start with Starter", href: "#" },
  },
  {
    name: "Pro",
    price: "$49",
    period: "/month",
    description: "For growing teams that need room to move fast.",
    features: [
      "Unlimited projects",
      "250k monthly page views",
      "Advanced funnels & cohorts",
      "A/B testing built in",
      "Custom domains & SSL",
      "5 team seats",
      "Priority support",
    ],
    cta: { label: "Get started with Pro", href: "#" },
    highlighted: true,
  },
  {
    name: "Enterprise",
    price: "Custom",
    description: "For organizations with security and scale requirements.",
    features: [
      "Everything in Pro",
      "Unlimited page views",
      "SSO / SAML & audit logs",
      "99.99% uptime SLA",
      "Unlimited team seats",
      "Dedicated success manager",
    ],
    cta: { label: "Talk to sales", href: "#" },
  },
]

export function Pricing01({
  eyebrow = "Pricing",
  title = "Simple pricing that scales with you",
  description = "Start free, upgrade when you outgrow it. Every plan includes unlimited visitors on your published pages — no surprise overages.",
  highlightLabel = "Most popular",
  tiers = defaultTiers,
  as: Heading = "h2",
  className,
}: Pricing01Props) {
  return (
    <section className={cn("bg-background px-6 py-24 md:py-32", className)}>
      <div className="mx-auto max-w-6xl">
        <div className="mx-auto max-w-2xl text-center">
          <Reveal>
            <p className="text-sm font-medium tracking-widest text-primary uppercase">
              {eyebrow}
            </p>
          </Reveal>
          <Reveal delay={0.1}>
            <Heading className="mt-4 text-balance font-heading text-4xl font-semibold tracking-tight md:text-5xl">
              {title}
            </Heading>
          </Reveal>
          <Reveal delay={0.2}>
            <p className="mt-5 text-pretty text-muted-foreground md:text-lg">
              {description}
            </p>
          </Reveal>
        </div>

        <motion.div
          initial="hidden"
          whileInView="visible"
          viewport={viewport}
          variants={stagger(0.12)}
          className="mt-14 grid gap-6 md:mt-16 md:grid-cols-3"
        >
          {tiers.map((tier) => (
            <motion.div key={tier.name} variants={fadeUp} className="relative">
              {tier.highlighted && (
                <div
                  aria-hidden
                  className="absolute -inset-4 rounded-[2rem] bg-primary/15 blur-2xl"
                />
              )}
              <div
                className={cn(
                  "relative flex h-full flex-col rounded-3xl border bg-card p-8",
                  tier.highlighted && "border-primary/50 shadow-lg"
                )}
              >
                {tier.highlighted && (
                  <Badge className="absolute -top-3 left-1/2 -translate-x-1/2 rounded-full px-3">
                    {highlightLabel}
                  </Badge>
                )}

                <h3 className="font-heading text-lg font-semibold tracking-tight">
                  {tier.name}
                </h3>
                <p className="mt-2 text-sm text-pretty text-muted-foreground">
                  {tier.description}
                </p>

                <div className="mt-6 flex items-baseline gap-1.5">
                  <span className="font-heading text-5xl font-semibold tracking-tighter">
                    {tier.price}
                  </span>
                  {tier.period && (
                    <span className="text-sm text-muted-foreground">
                      {tier.period}
                    </span>
                  )}
                </div>

                <ul className="mt-8 mb-8 flex flex-col gap-3">
                  {tier.features.map((feature) => (
                    <li key={feature} className="flex items-start gap-3">
                      <span className="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full bg-primary/10">
                        <Check className="size-3 text-primary" />
                      </span>
                      <span className="text-sm text-muted-foreground">
                        {feature}
                      </span>
                    </li>
                  ))}
                </ul>

                <a
                  href={tier.cta.href}
                  className={cn(
                    buttonVariants({
                      size: "lg",
                      variant: tier.highlighted ? "default" : "outline",
                    }),
                    "mt-auto w-full"
                  )}
                >
                  {tier.cta.label}
                </a>
              </div>
            </motion.div>
          ))}
        </motion.div>
      </div>
    </section>
  )
}