{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pricing-2",
  "title": "Pricing 2: three tiers with billing toggle",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Three plan cards with a monthly/yearly switch. Prices roll to the new value, a savings badge and billed-total line update, and the middle plan is lifted with a border beam.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/animated-number-flow.json",
    "https://ui.ballmac.com/r/badge.json",
    "https://ui.ballmac.com/r/border-beam.json",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/segmented-control.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/pricing-2/pricing-2.tsx",
      "content": "// Ballmac UI: Pricing 2. https://ui.ballmac.com/blocks/pricing-2\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, Check } from \"lucide-react\"\n\nimport { AnimatedNumberFlow } from \"@/components/ballmac/animated-number-flow\"\nimport { Badge } from \"@/components/ballmac/badge\"\nimport { BorderBeam } from \"@/components/ballmac/border-beam\"\nimport { buttonVariants } from \"@/components/ballmac/button\"\nimport { SegmentedControl, SegmentedControlItem } from \"@/components/ballmac/segmented-control\"\nimport { cn } from \"@/lib/utils\"\n\ntype Pricing2Interval = \"monthly\" | \"yearly\"\n\ntype Pricing2Plan = {\n  /** Plan name. */\n  name: string\n  /** One sentence about who it is for. */\n  description: string\n  /**\n   * Price per month. Give `{ monthly, yearly }` where `yearly` is the per-month price when billed\n   * annually, or a string such as \"Custom\" for a plan without a number.\n   */\n  price: { monthly: number; yearly: number } | string\n  /** What the price is for, e.g. \"per seat\". */\n  unit?: string\n  /** Lead-in line above the feature list, e.g. \"Everything in Starter, plus:\". */\n  lead?: string\n  /** Included features. */\n  features: string[]\n  /** Button. */\n  cta: { label: string; href: string }\n  /** Emphasise this plan. */\n  featured?: boolean\n}\n\ntype Pricing2Props = Omit<React.ComponentProps<\"section\">, \"title\"> & {\n  /** Section heading. */\n  title?: string\n  /** One sentence under the heading. */\n  description?: string\n  /** Three plans read best. */\n  plans?: Pricing2Plan[]\n  /** Billing interval shown first. */\n  defaultInterval?: Pricing2Interval\n  /** Controlled billing interval. */\n  interval?: Pricing2Interval\n  /** Called when the visitor switches interval. */\n  onIntervalChange?: (interval: Pricing2Interval) => void\n  /** Badge next to the yearly option. Set to an empty string to hide it. */\n  discountLabel?: string\n  /** Currency code for the numeric prices. */\n  currency?: string\n  /** Small print under the plans. */\n  note?: string\n  /** Strip under the plans for a sales-led offer. Set to null to hide it. */\n  enterprise?: { title: string; description: string; cta: { label: string; href: string } } | null\n}\n\nconst defaultPlans: Pricing2Plan[] = [\n  {\n    name: \"Starter\",\n    description: \"For individuals and small side projects.\",\n    price: { monthly: 12, yearly: 9 },\n    unit: \"per month\",\n    features: [\"Up to 3 projects\", \"10 GB storage\", \"Basic analytics\", \"Community support\"],\n    cta: { label: \"Start free\", href: \"#\" },\n  },\n  {\n    name: \"Pro\",\n    description: \"For growing teams that ship every week.\",\n    price: { monthly: 32, yearly: 25 },\n    unit: \"per seat / month\",\n    lead: \"Everything in Starter, plus:\",\n    features: [\"Unlimited projects\", \"1 TB storage\", \"Advanced analytics\", \"Team roles and SSO\", \"Priority support\"],\n    cta: { label: \"Start 14-day trial\", href: \"#\" },\n    featured: true,\n  },\n  {\n    name: \"Scale\",\n    description: \"For companies with security and volume needs.\",\n    price: { monthly: 79, yearly: 63 },\n    unit: \"per seat / month\",\n    lead: \"Everything in Pro, plus:\",\n    features: [\"Audit log and SCIM\", \"Custom data retention\", \"99.99% uptime SLA\", \"Dedicated success manager\"],\n    cta: { label: \"Talk to sales\", href: \"#\" },\n  },\n]\n\nfunction Pricing2({\n  title = \"Plans that grow with your team.\",\n  description = \"Start free for 14 days. Switch or cancel whenever you like.\",\n  plans = defaultPlans,\n  defaultInterval = \"yearly\",\n  interval: intervalProp,\n  onIntervalChange,\n  discountLabel = \"Save 20%\",\n  currency = \"USD\",\n  note = \"Prices in USD, billed per workspace. Taxes may apply.\",\n  enterprise = {\n    title: \"Need something custom?\",\n    description: \"Volume pricing, private cloud and procurement support for 500+ seats.\",\n    cta: { label: \"Contact sales\", href: \"#\" },\n  },\n  className,\n  ...props\n}: Pricing2Props) {\n  const [internal, setInternal] = React.useState<Pricing2Interval>(defaultInterval)\n  const interval = intervalProp ?? internal\n  const yearly = interval === \"yearly\"\n  const format = React.useMemo<Intl.NumberFormatOptions>(() => ({ style: \"currency\", currency, maximumFractionDigits: 0 }), [currency])\n\n  function change(next: string) {\n    const value = next as Pricing2Interval\n    if (intervalProp === undefined) setInternal(value)\n    onIntervalChange?.(value)\n  }\n\n  return (\n    <section data-slot=\"pricing-2\" className={cn(\"mx-auto max-w-6xl px-4 py-20 sm:px-6 md:py-28\", className)} {...props}>\n      <div className=\"mx-auto max-w-2xl text-center\">\n        <h2 className=\"text-3xl font-semibold tracking-[-0.035em] text-balance sm:text-4xl lg:text-5xl\">{title}</h2>\n        <p className=\"text-muted-foreground mt-4 text-lg text-pretty\">{description}</p>\n        <div className=\"mt-8 flex flex-wrap items-center justify-center gap-3\">\n          <SegmentedControl aria-label=\"Billing interval\" value={interval} onValueChange={change}>\n            <SegmentedControlItem value=\"monthly\">Monthly</SegmentedControlItem>\n            <SegmentedControlItem value=\"yearly\">Yearly</SegmentedControlItem>\n          </SegmentedControl>\n          {discountLabel && (\n            <Badge status=\"success\" className={cn(\"transition-opacity duration-200\", !yearly && \"opacity-60\")}>\n              {discountLabel}\n            </Badge>\n          )}\n        </div>\n        <p role=\"status\" className=\"sr-only\">{yearly ? \"Showing yearly prices, billed annually.\" : \"Showing monthly prices.\"}</p>\n      </div>\n\n      <div className=\"mt-12 grid items-stretch gap-5 lg:grid-cols-3\">\n        {plans.map((plan) => {\n          const numeric = typeof plan.price === \"string\" ? null : plan.price\n          const amount = numeric ? (yearly ? numeric.yearly : numeric.monthly) : null\n          return (\n            <div\n              key={plan.name}\n              data-featured={plan.featured || undefined}\n              className={cn(\n                \"bg-card relative flex flex-col rounded-3xl border p-7 sm:p-8\",\n                plan.featured && \"border-foreground/20 shadow-[0_40px_90px_-45px_rgb(0_0_0/0.5)] lg:-my-3 lg:py-10\"\n              )}\n            >\n              {plan.featured && <BorderBeam duration={12} className=\"rounded-3xl\" />}\n              <div className=\"flex items-center justify-between gap-3\">\n                <h3 className=\"text-lg font-semibold\">{plan.name}</h3>\n                {plan.featured && <Badge>Most popular</Badge>}\n              </div>\n              <p className=\"text-muted-foreground mt-2 text-sm text-pretty lg:min-h-10\">{plan.description}</p>\n              <div className=\"mt-7 flex items-baseline gap-2\">\n                {amount === null ? (\n                  <span className=\"text-5xl font-semibold tracking-[-0.04em]\">{plan.price as string}</span>\n                ) : (\n                  <AnimatedNumberFlow value={amount} format={format} fade className=\"text-5xl font-semibold tracking-[-0.04em]\" />\n                )}\n                {plan.unit && amount !== null && <span className=\"text-muted-foreground text-sm\">{plan.unit}</span>}\n              </div>\n              <p className=\"text-muted-foreground mt-1.5 h-5 text-xs\">\n                {numeric && (yearly ? `Billed ${new Intl.NumberFormat(\"en-US\", format).format(numeric.yearly * 12)} per year` : \"Billed monthly\")}\n              </p>\n              <a\n                href={plan.cta.href}\n                className={buttonVariants({ variant: plan.featured ? \"default\" : \"outline\", size: \"lg\", shape: \"pill\", className: \"mt-6 w-full\" })}\n              >\n                {plan.cta.label}\n                {plan.featured && <ArrowRight  className=\"rtl:rotate-180\"/>}\n              </a>\n              <div className=\"mt-7 border-t pt-6\">\n                {plan.lead && <p className=\"mb-3 text-sm font-medium\">{plan.lead}</p>}\n                <ul className=\"space-y-3 text-sm\">\n                  {plan.features.map((f) => (\n                    <li key={f} className=\"flex items-start gap-3\">\n                      <Check className=\"text-chart-2 mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n                      {f}\n                    </li>\n                  ))}\n                </ul>\n              </div>\n            </div>\n          )\n        })}\n      </div>\n\n      {enterprise && (\n        <div className=\"bg-muted/40 mt-8 flex flex-col items-start gap-4 rounded-3xl border p-6 sm:flex-row sm:items-center sm:justify-between sm:p-8\">\n          <div>\n            <p className=\"font-semibold\">{enterprise.title}</p>\n            <p className=\"text-muted-foreground mt-1 text-sm\">{enterprise.description}</p>\n          </div>\n          <a className={buttonVariants({ variant: \"outline\", shape: \"pill\" })} href={enterprise.cta.href}>\n            {enterprise.cta.label} <ArrowRight  className=\"rtl:rotate-180\"/>\n          </a>\n        </div>\n      )}\n      {note && <p className=\"text-muted-foreground mt-8 text-center text-sm\">{note}</p>}\n    </section>\n  )\n}\n\nexport { Pricing2, type Pricing2Props, type Pricing2Plan, type Pricing2Interval }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/pricing-2/pricing-2.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "pricing",
      "plans",
      "toggle",
      "yearly",
      "saas",
      "tiers"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "Standard SaaS pricing with a billing toggle. Pass plans=[{ name, description, price: { monthly, yearly } | string, unit?, lead?, features, cta, featured? }].",
      "whenToUse": [
        "SaaS products with three plans",
        "Products that discount annual billing"
      ],
      "whenNotToUse": [
        "Two plans only (use pricing-1)",
        "Usage-based pricing (use plan-selector or a calculator)"
      ],
      "composesWith": [
        "faq-2",
        "logo-cloud-1",
        "cta-1",
        "pricing-3"
      ],
      "a11y": [
        {
          "keys": "Arrow Left / Right",
          "action": "Switches billing interval"
        },
        {
          "keys": "Status message",
          "action": "Announces the interval being shown"
        }
      ],
      "customization": [
        "plans: price is { monthly, yearly } per month or a string like 'Custom'",
        "defaultInterval / interval / onIntervalChange",
        "discountLabel, currency, note, enterprise (null to hide)"
      ]
    },
    "examples": [
      "pricing-2-demo",
      "pricing-2-custom"
    ],
    "url": "https://ui.ballmac.com/components/pricing-2"
  },
  "categories": [
    "blocks",
    "pricing"
  ],
  "type": "registry:block"
}