# Pricing Tiers

> Toggleable pricing tiers with editable CONTENT and TIERS.

A shadcn/ui block from the [@atroui](https://www.atroui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/atroui/pricing-tiers).

- Page, with a live preview: https://shadcn.exchange/r/atroui/pricing-tiers
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/atroui/pricing-tiers-light-f18247c7d60292ad.png
- Registry item: https://www.atroui.com/r/pricing-tiers.json
- Listed: 2026-10-25, source last changed 2026-10-25

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/atroui/pricing-tiers.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`, `next`

## Files

### `registry/default/blocks/pricing-tiers.tsx`

```tsx
"use client"

import { useState } from "react"
import Link from "next/link"
import { ArrowRight, Check } from "lucide-react"

/**
 * Edit CONTENT / TIERS to match your pricing. Monthly/yearly toggle with
 * aria-pressed, one highlighted tier. Self-contained — prices are plain
 * spans (no sibling ui imports).
 * After install this file lives in your repo.
 */
const CONTENT = {
  stamp: "Pricing",
  headlineBefore: "Simple plans,",
  headlineAccent: "cancel anytime",
  headlineAfter: ".",
  lede: "Start free, upgrade when it pays for itself. Prices in USD.",
  toggleMonthly: "Monthly",
  toggleYearly: "Yearly",
  yearlyNote: "2 months free",
}

type Billing = "monthly" | "yearly"

const TIERS = [
  {
    name: "Starter",
    blurb: "For side projects finding shape.",
    monthly: "$12",
    yearly: "$10",
    per: "/mo",
    billed: { monthly: "Billed monthly", yearly: "Billed yearly" },
    cta: { label: "Start free", href: "/signup?plan=starter" },
    features: ["3 projects", "Community support", "Basic analytics"],
    highlight: false,
  },
  {
    name: "Pro",
    blurb: "For teams shipping every week.",
    monthly: "$29",
    yearly: "$24",
    per: "/mo",
    billed: { monthly: "Billed monthly", yearly: "Billed yearly" },
    cta: { label: "Start 14-day trial", href: "/signup?plan=pro" },
    features: [
      "Unlimited projects",
      "Priority support",
      "Advanced analytics",
      "Custom domains",
    ],
    highlight: true,
    badge: "Most popular",
  },
  {
    name: "Scale",
    blurb: "For products with real traffic.",
    monthly: "$79",
    yearly: "$66",
    per: "/mo",
    billed: { monthly: "Billed monthly", yearly: "Billed yearly" },
    cta: { label: "Talk to us", href: "/contact?plan=scale" },
    features: ["Everything in Pro", "SSO & audit logs", "Dedicated manager"],
    highlight: false,
  },
]

export function PricingTiers() {
  const [billing, setBilling] = useState<Billing>("monthly")

  return (
    <section className="@container border-t border-border-subtle bg-background text-foreground">
      <div className="border-b border-border-subtle">
        <div className="mx-auto max-w-7xl border-x border-border-subtle px-6 py-12 @min-[40rem]:px-10 @min-[40rem]:py-16">
          <div className="mx-auto max-w-2xl text-center">
            <p className="text-[11px] font-semibold tracking-[0.14em] text-muted-foreground uppercase">
              {CONTENT.stamp}
            </p>
            <h2 className="mt-4 text-3xl font-medium tracking-tight text-foreground @min-[40rem]:text-5xl">
              {CONTENT.headlineBefore}{" "}
              <span className="italic text-brand">{CONTENT.headlineAccent}</span>
              {CONTENT.headlineAfter}
            </h2>
            <p className="mt-4 text-[15px] leading-relaxed text-muted-foreground">
              {CONTENT.lede}
            </p>

            <div
              role="group"
              aria-label="Billing period"
              className="mt-6 inline-flex items-center gap-1 rounded-[var(--atro-control-radius)] border border-border-subtle bg-muted/50 p-1"
            >
              {(["monthly", "yearly"] as const).map((period) => (
                <button
                  key={period}
                  type="button"
                  aria-pressed={billing === period}
                  onClick={() => setBilling(period)}
                  className={
                    billing === period
                      ? "rounded-[calc(var(--atro-control-radius)-4px)] bg-background px-4 py-1.5 text-sm font-medium text-foreground shadow-sm ring-1 ring-border-subtle"
                      : "rounded-[calc(var(--atro-control-radius)-4px)] px-4 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
                  }
                >
                  {period === "monthly"
                    ? CONTENT.toggleMonthly
                    : CONTENT.toggleYearly}
                </button>
              ))}
            </div>
            {billing === "yearly" ? (
              <p className="mt-2 text-xs font-medium text-brand">
                {CONTENT.yearlyNote}
              </p>
            ) : null}
          </div>

          <ul className="mx-auto mt-10 grid max-w-5xl grid-cols-1 gap-4 @min-[64rem]:grid-cols-3">
            {TIERS.map((tier) => (
              <li
                key={tier.name}
                className={
                  tier.highlight
                    ? "flex flex-col rounded-[var(--atro-panel-radius)] border bg-card p-6 ring-1 @min-[40rem]:p-7 border-[color-mix(in_oklch,var(--brand)_50%,var(--border-subtle))] ring-[color-mix(in_oklch,var(--brand)_30%,transparent)]"
                    : "flex flex-col rounded-[var(--atro-panel-radius)] border border-border-subtle bg-card p-6 @min-[40rem]:p-7"
                }
              >
                {tier.highlight && "badge" in tier && tier.badge ? (
                  <p className="mb-3 inline-flex w-fit items-center rounded-full bg-[color-mix(in_oklch,var(--brand)_12%,transparent)] px-2.5 py-1 text-[11px] font-semibold tracking-wide text-brand uppercase">
                    {tier.badge}
                  </p>
                ) : null}
                <h3 className="text-lg font-medium text-foreground">
                  {tier.name}
                </h3>
                <p className="mt-1 text-sm text-muted-foreground">
                  {tier.blurb}
                </p>
                <p className="mt-5 flex items-baseline gap-1">
                  <span
                    aria-live="polite"
                    className="text-4xl font-medium tracking-tight tabular-nums text-foreground"
                  >
                    {billing === "monthly" ? tier.monthly : tier.yearly}
                  </span>
                  <span className="text-sm text-muted-foreground">
                    {tier.per}
                  </span>
                </p>
                <p className="mt-1 text-xs text-muted-foreground">
                  {billing === "monthly" ? tier.billed.monthly : tier.billed.yearly}
                </p>
                <ul className="mt-5 space-y-2 border-t border-border-subtle pt-5">
                  {tier.features.map((feature) => (
                    <li
                      key={feature}
                      className="flex items-start gap-2 text-sm text-foreground/90"
                    >
                      <Check
                        className="mt-0.5 size-3.5 shrink-0 text-brand"
                        strokeWidth={2.5}
                        aria-hidden
                      />
                      <span>{feature}</span>
                    </li>
                  ))}
                </ul>
                <Link
                  href={tier.cta.href}
                  className={
                    tier.highlight
                      ? "atro-btn mt-6 w-full justify-center"
                      : "atro-btn-ghost mt-6 w-full justify-center"
                  }
                >
                  {tier.cta.label}
                  <ArrowRight className="size-3.5" aria-hidden />
                </Link>
              </li>
            ))}
          </ul>
        </div>
      </div>
    </section>
  )
}
```
