# Pricing with billing toggle

> Three-tier pricing with an animated monthly / yearly toggle and a highlighted plan.

A shadcn/ui block from the [@balick](https://ui.balick.me) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/balick/pricing-01).

- Page, with a live preview: https://shadcn.exchange/r/balick/pricing-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/balick/pricing-01-light-2b9f0aafd9ae8d5c.png
- Registry item: https://ui.balick.me/r/pricing-01.json
- Categories: pricing
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://ui.balick.me/r/pricing-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `button`, `https://ui.balick.me/r/animated-number.json`, `https://ui.balick.me/r/section.json`, `https://ui.balick.me/r/segmented-control.json`

## Files

### `registry/balick/blocks/pricing-01/pricing-01.tsx`

```tsx
"use client"

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

import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"
import { AnimatedNumber } from "@/registry/balick/ui/animated-number"
import { Section, SectionHeader } from "@/registry/balick/ui/section"
import { SegmentedControl } from "@/registry/balick/ui/segmented-control"

type Billing = "monthly" | "yearly"

interface Plan {
  name: string
  description: string
  price: { monthly: number; yearly: number } | null
  cta: string
  featured?: boolean
  features: string[]
}

const plans: Plan[] = [
  {
    name: "Hobby",
    description: "For side projects and experiments.",
    price: { monthly: 0, yearly: 0 },
    cta: "Start for free",
    features: ["1 project", "10k monthly events", "Community support", "7-day data retention"],
  },
  {
    name: "Pro",
    description: "For growing teams shipping every day.",
    price: { monthly: 24, yearly: 19 },
    cta: "Start 14-day trial",
    featured: true,
    features: [
      "Unlimited projects",
      "1M monthly events",
      "Priority support",
      "90-day data retention",
      "Team roles & SSO",
    ],
  },
  {
    name: "Enterprise",
    description: "For organisations with custom needs.",
    price: null,
    cta: "Contact sales",
    features: [
      "Everything in Pro",
      "Unlimited events",
      "Dedicated manager",
      "Custom contracts & SLA",
      "Audit logs",
    ],
  },
]

const billingOptions = [
  { value: "monthly" as const, label: "Monthly" },
  { value: "yearly" as const, label: "Yearly", badge: "−20%" },
]

function Price({ plan, billing }: { plan: Plan; billing: Billing }) {
  if (!plan.price) {
    return <span className="text-4xl font-semibold tracking-tighter">Custom</span>
  }
  const amount = plan.price[billing]

  return (
    <div className="flex items-baseline">
      <span className="text-4xl font-semibold tracking-tighter">$</span>
      <AnimatedNumber value={amount} className="text-4xl font-semibold tracking-tighter" />
      <span className="ml-2 text-sm opacity-60">/ user / month</span>
    </div>
  )
}

export function Pricing01({ id = "pricing" }: { id?: string }) {
  const [billing, setBilling] = React.useState<Billing>("yearly")

  return (
    <Section id={id}>
      <SectionHeader
        eyebrow="Pricing"
        title="Simple pricing that scales with you."
        description="Start for free, upgrade when you need to. No hidden fees."
      >
        <SegmentedControl
          aria-label="Billing period"
          value={billing}
          onValueChange={setBilling}
          options={billingOptions}
        />
      </SectionHeader>

      <div className="mt-16 grid grid-cols-1 gap-4 lg:grid-cols-3 lg:items-start">
        {plans.map((plan) => (
          <div
            key={plan.name}
            className={cn(
              "relative flex flex-col rounded-2xl border p-8",
              plan.featured
                ? "border-foreground bg-foreground text-background shadow-[0_24px_64px_-24px_rgb(0_0_0/0.35)]"
                : "bg-card"
            )}
          >
            <div className="flex items-center justify-between">
              <h3 className="font-medium">{plan.name}</h3>
              {plan.featured && (
                <span className="rounded-full bg-background/15 px-2.5 py-0.5 text-xs font-medium">
                  Most popular
                </span>
              )}
            </div>
            <p className={cn("mt-2 text-sm", plan.featured ? "opacity-70" : "text-muted-foreground")}>
              {plan.description}
            </p>
            <div className="mt-8 h-10">
              <Price plan={plan} billing={billing} />
            </div>
            <p className={cn("mt-1 h-5 text-xs", plan.featured ? "opacity-60" : "text-muted-foreground")}>
              {plan.price && plan.price.monthly > 0 && billing === "yearly" ? "Billed annually" : ""}
            </p>
            <Button
              size="lg"
              variant={plan.featured ? "secondary" : "outline"}
              className="mt-6 w-full"
            >
              {plan.cta}
            </Button>
            <ul className="mt-8 flex flex-col gap-3 text-sm">
              {plan.features.map((feature) => (
                <li key={feature} className="flex items-center gap-3">
                  <Check className={cn("size-4 shrink-0", plan.featured ? "opacity-80" : "text-muted-foreground")} />
                  {feature}
                </li>
              ))}
            </ul>
          </div>
        ))}
      </div>

      <p className="mt-10 text-center text-sm text-muted-foreground">
        Prices in USD. Taxes may apply. Need something else?{" "}
        <a href="#" className="text-foreground underline underline-offset-4">
          Talk to us
        </a>
        .
      </p>
    </Section>
  )
}
```
