# Pricing Section v2

> Four-tier pricing grid with a monthly/yearly billing toggle, per-tier feature lists, and card-based layout.

A shadcn/ui block from the [@uiception](https://uiception.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/pricing-section-v2).

- Page, with a live preview: https://shadcn.exchange/r/uiception/pricing-section-v2
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/pricing-section-v2-light-adf27ddf0bd5e5bc.png
- Registry item: https://uiception.com/r/pricing-section-v2.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiception.com/r/pricing-section-v2.json
```

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

## Dependencies

- Registry items: `button`, `card`

## Files

### `registry/new-york/blocks/pricing-section-v2/page.tsx`

```tsx
import PricingSectionV2 from "./components/pricing-section-v2"

export default function Page() {
  return <PricingSectionV2 />
}
```

### `registry/new-york/blocks/pricing-section-v2/components/pricing-section-v2.tsx`

```tsx
"use client"

import { useState } from "react"

import { cn } from "@/lib/utils"

import PricingCard from "./pricing-card"
import { pricingPlans, type BillingCycle } from "../lib/pricing-plans"

export default function PricingSectionV2() {
  const [billingCycle, setBillingCycle] = useState<BillingCycle>("monthly")

  return (
    <section className="py-4 md:py-6 lg:py-8">
      <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
        <div className="mx-auto mb-10 max-w-3xl text-center">
          <h2 className="text-4xl font-medium tracking-tight text-balance md:text-5xl">
            Pricing
          </h2>
          <div className="mt-6 flex justify-center">
            <fieldset>
              <legend className="sr-only">Billing frequency</legend>
              <div className="relative inline-grid grid-cols-2 rounded-full bg-muted p-0.5">
                <div
                  aria-hidden
                  className={cn(
                    "pointer-events-none absolute top-0.5 bottom-0.5 rounded-full border border-border bg-background shadow-sm transition-transform duration-200 ease-out",
                    billingCycle === "yearly" && "translate-x-full"
                  )}
                  style={{ left: "2px", width: "calc(50% - 2px)" }}
                />
                <label className="relative z-10 cursor-pointer rounded-full px-5 py-1.5 text-center">
                  <input
                    checked={billingCycle === "monthly"}
                    className="sr-only"
                    name="billing"
                    type="radio"
                    value="monthly"
                    onChange={() => setBillingCycle("monthly")}
                  />
                  <span
                    className={cn(
                      "text-sm font-medium leading-none transition-colors",
                      billingCycle === "monthly"
                        ? "text-foreground"
                        : "text-muted-foreground"
                    )}
                  >
                    Monthly
                  </span>
                </label>
                <label className="relative z-10 cursor-pointer rounded-full px-5 py-1.5 text-center">
                  <input
                    checked={billingCycle === "yearly"}
                    className="sr-only"
                    name="billing"
                    type="radio"
                    value="yearly"
                    onChange={() => setBillingCycle("yearly")}
                  />
                  <span
                    className={cn(
                      "text-sm font-medium leading-none transition-colors",
                      billingCycle === "yearly"
                        ? "text-foreground"
                        : "text-muted-foreground"
                    )}
                  >
                    Yearly
                  </span>
                </label>
              </div>
            </fieldset>
          </div>
        </div>
        <ul className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
          {pricingPlans.map((plan) => (
            <PricingCard
              key={plan.id}
              billingCycle={billingCycle}
              plan={plan}
            />
          ))}
        </ul>
      </div>
    </section>
  )
}
```

### `registry/new-york/blocks/pricing-section-v2/components/pricing-card.tsx`

```tsx
import { CheckIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import type { BillingCycle, PricingPlan } from "../lib/pricing-plans"

type PricingCardProps = {
  plan: PricingPlan
  billingCycle: BillingCycle
}

export default function PricingCard({ plan, billingCycle }: PricingCardProps) {
  const pricing = billingCycle === "yearly" ? plan.yearly : plan.monthly

  return (
    <li className="min-w-0">
      <Card className="flex h-full flex-col justify-between rounded-sm p-5 shadow-none gap-4">
        <div>
          <p className="text-3xl font-semibold tracking-tight text-foreground">{plan.name}</p>
          <p className="mt-2 text-2xl font-medium text-muted-foreground">
            {pricing.price}
          </p>
          <p className="mt-4 text-sm text-foreground">{plan.intro}</p>
          <ul className="mt-2 flex flex-col gap-1.5">
            {plan.features.map((feature) => (
              <li
                key={feature}
                className="flex items-start gap-2 text-xs leading-relaxed font-light text-foreground"
              >
                <CheckIcon
                  aria-hidden
                  className="mt-0.5 size-3.5 shrink-0 text-foreground"
                />
                <span>{feature}</span>
              </li>
            ))}
          </ul>
        </div>
        <div className="mt-6">
          <Button
            size="lg"
            className="rounded-full"
            variant={plan.ctaVariant ?? "default"}
          >
            {plan.ctaLabel}
          </Button>
        </div>
      </Card>
    </li>
  )
}
```

### `registry/new-york/blocks/pricing-section-v2/lib/pricing-plans.ts`

```ts
export type BillingCycle = "monthly" | "yearly"

export type PricingPlan = {
  id: "hobby" | "pro" | "teams" | "enterprise"
  name: string
  monthly: { price: string }
  yearly: { price: string }
  intro: string
  features: string[]
  ctaLabel: string
  ctaVariant?: "default" | "outline"
}

export const pricingPlans: PricingPlan[] = [
  {
    id: "hobby",
    name: "Hobby",
    monthly: { price: "Free" },
    yearly: { price: "Free" },
    intro: "Includes:",
    features: [
      "1 workspace",
      "Up to 3 active projects",
      "100 tasks per month",
      "Basic workflow templates",
    ],
    ctaLabel: "Get started",
    ctaVariant: "outline",
  },
  {
    id: "pro",
    name: "Pro",
    monthly: { price: "$20" },
    yearly: { price: "$16" },
    intro: "Everything in Hobby, plus:",
    features: [
      "Unlimited active projects",
      "Unlimited tasks",
      "Custom automations",
      "50+ app integrations",
      "Priority email support",
    ],
    ctaLabel: "Get Pro",
  },
  {
    id: "teams",
    name: "Teams",
    monthly: { price: "$40" },
    yearly: { price: "$32" },
    intro: "Everything in Pro, plus:",
    features: [
      "Up to 25 seats",
      "Shared dashboards and reporting",
      "Role-based access control",
      "Admin controls and permissions",
      "SSO and SAML",
      "SLA-backed uptime",
    ],
    ctaLabel: "Get Teams",
  },
  {
    id: "enterprise",
    name: "Enterprise",
    monthly: { price: "Custom" },
    yearly: { price: "Custom" },
    intro: "Everything in Teams, plus:",
    features: [
      "Unlimited seats",
      "Dedicated infrastructure",
      "Custom data retention policies",
      "SOC 2 Type II compliance",
      "Tailored onboarding",
      "24/7 dedicated support",
    ],
    ctaLabel: "Contact sales",
    ctaVariant: "outline",
  },
]
```
