# Pricing

The Pricing block lays out a row of plans and traces the recommended one with a travelling Border Beam, so the tier you want chosen is the one that moves rather than the one wearing another badge.

- **Category:** Block
- **Docs:** https://uibeats.com/docs/block/pricing
- **Registry:** https://uibeats.com/r/pricing.json
- **License:** MIT
- **npm dependencies:** `lucide-react`

## Installation

```bash
npx shadcn@latest add https://uibeats.com/r/pricing.json
```

Or install the dependencies and copy the source below:

```bash
npm install lucide-react
```

## When to use

For a pricing section where one plan is the one you want chosen. Mark exactly one tier as featured; if every plan is equal, a plain grid says it better.

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `tiers` | `PricingTier[]` | `-` | The plans, in display order (required) |
| `heading` | `string` | `-` | Section heading above the tiers |
| `description` | `string` | `-` | Supporting line under the heading |
| `PricingTier: name` | `string` | `-` | Plan name, e.g. Pro |
| `PricingTier: price` | `string` | `-` | Headline price, as text so Free and $19 both work |
| `PricingTier: period` | `string` | `-` | Shown after the price, e.g. /month |
| `PricingTier: features` | `string[]` | `-` | Ticked list of what the plan includes |
| `PricingTier: action` | `{ label: string; href: string }` | `-` | The plan's call to action |
| `PricingTier: featured` | `boolean` | `false` | Traces the card with a Border Beam and promotes its button. Only the first tier claiming it is honoured |
| `className` | `string` | `''` | Additional CSS classes for styling |

## Example usage

```tsx
"use client";
import { Pricing } from "@/components/demo/block/pricing";

const TIERS = [
  {
    name: "Hobby",
    price: "Free",
    description: "Everything you need to start.",
    features: ["All 40 components", "MIT licensed", "Community support"],
    action: { label: "Get started", href: "#" },
  },
  {
    name: "Pro",
    price: "$19",
    period: "/month",
    description: "For teams shipping regularly.",
    features: ["Everything in Hobby", "Private registry", "Priority support"],
    action: { label: "Start free trial", href: "#" },
    featured: true,
  },
  {
    name: "Team",
    price: "$49",
    period: "/month",
    description: "For a whole design system.",
    features: ["Everything in Pro", "Shared themes", "Onboarding call"],
    action: { label: "Contact sales", href: "#" },
  },
];

const PricingUsage = () => {
  return (
    <Pricing
      heading="Priced to start free"
      description="Every component is MIT licensed. Pay only when you want the extras."
      tiers={TIERS}
    />
  );
};

export default PricingUsage;
```

## Source

Save as `components/ui/pricing.tsx`.

```tsx
"use client";

import { Check } from "lucide-react";
import { BorderBeam } from "@/components/demo/component/border-beam";
import { ShimmerButton } from "@/components/demo/button/shimmer-button";
import { cn } from "@/lib/utils";

export interface PricingTier {
  name: string;
  price: string;
  /** Shown after the price, e.g. "/month". */
  period?: string;
  description?: string;
  features: string[];
  action: { label: string; href: string };
  /** Traces the card with a Border Beam and leads with the primary button. */
  featured?: boolean;
}

interface PricingProps {
  heading?: string;
  description?: string;
  tiers: PricingTier[];
  className?: string;
}

/**
 * A pricing section, with the recommended tier traced by a Border Beam.
 *
 * The beam is the whole reason this is a block rather than three cards in a
 * grid: singling out a plan usually means a coloured border and a "Most
 * popular" pill, both of which are static. A light that travels the recommended
 * card's edge draws the eye without adding another badge to read.
 *
 * Exactly one tier should carry `featured`. Two beams racing each other is not
 * emphasis, it is decoration, so the component only reads the first.
 */
export function Pricing({
  heading,
  description,
  tiers,
  className = "",
}: PricingProps) {
  // Emphasis is a comparison, so only the first claim to it is honoured.
  const featuredIndex = tiers.findIndex((tier) => tier.featured);

  return (
    <section className={cn("bg-background px-6 py-24", className)}>
      {heading || description ? (
        <div className="mx-auto mb-14 max-w-2xl text-center">
          {heading ? (
            <h2 className="text-3xl font-bold tracking-tighter text-balance sm:text-4xl">
              {heading}
            </h2>
          ) : null}
          {description ? (
            <p className="mt-3 text-balance text-muted-foreground">
              {description}
            </p>
          ) : null}
        </div>
      ) : null}

      <div className="mx-auto grid max-w-5xl gap-6 sm:grid-cols-2 lg:grid-cols-3">
        {tiers.map((tier, index) => {
          const isFeatured = index === featuredIndex;

          const card = (
            <div
              className={cn(
                "flex h-full flex-col rounded-[inherit] p-6",
                !isFeatured && "rounded-xl border bg-card",
              )}
            >
              <p className="text-sm font-medium">{tier.name}</p>
              <p className="mt-3 flex items-baseline gap-1">
                <span className="text-4xl font-bold tracking-tighter">
                  {tier.price}
                </span>
                {tier.period ? (
                  <span className="text-sm text-muted-foreground">
                    {tier.period}
                  </span>
                ) : null}
              </p>
              {tier.description ? (
                <p className="mt-2 text-sm text-muted-foreground">
                  {tier.description}
                </p>
              ) : null}

              <ul className="mt-6 flex flex-1 flex-col gap-2.5">
                {tier.features.map((feature) => (
                  <li key={feature} className="flex items-start gap-2 text-sm">
                    <Check className="mt-0.5 size-4 shrink-0 text-primary" />
                    <span className="text-muted-foreground">{feature}</span>
                  </li>
                ))}
              </ul>

              <a href={tier.action.href} className="mt-7 block">
                {isFeatured ? (
                  <ShimmerButton className="w-full">
                    {tier.action.label}
                  </ShimmerButton>
                ) : (
                  <span className="inline-flex w-full items-center justify-center rounded-full border border-border px-6 py-2.5 text-sm font-medium transition-colors hover:bg-accent">
                    {tier.action.label}
                  </span>
                )}
              </a>
            </div>
          );

          return isFeatured ? (
            <BorderBeam
              key={tier.name}
              duration={6}
              arc={90}
              className="h-full"
            >
              {card}
            </BorderBeam>
          ) : (
            <div key={tier.name} className="h-full">
              {card}
            </div>
          );
        })}
      </div>
    </section>
  );
}

export default Pricing;
```

## Credits

Written by [Claude Code](https://claude.com/claude-code).
