# Pricing Credits Packs — Tailwind Pricing Section

> Pay-as-you-go credit packs as radio cards with growing coin stacks, a best-value corner ribbon and a selection ring that glides to the chosen pack.

- Category: [Pricing](https://pixelpapa.com/components/pricing.md)
- Tags: pricing, credits, packs, pay as you go, radio
- Dependencies: lucide-react, motion (plus React and Tailwind CSS v4)
- Last updated: 2026-11-13
- Live preview: https://pixelpapa.com/components/pricing/pricing-credits-packs

## Install

```bash
npx shadcn@latest add "https://pixelpapa.com/r/pricing-credits-packs.json"
```

The file is written to `components/pixelpapa/pricing-credits-packs.tsx`. To install manually, run `npm install lucide-react motion` and copy the source below into your project. It imports `cn` from `@/lib/utils`, which every shadcn project has.

## Source

```tsx
"use client";

import { ArrowRight, Zap } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
import { cn } from "@/lib/utils";

type Pack = { credits: number; price: number; name: string; best?: boolean };

const defaultPacks: Pack[] = [
  { name: "Sketch", credits: 500, price: 9 },
  { name: "Studio", credits: 2500, price: 39 },
  { name: "Production", credits: 10000, price: 129, best: true },
  { name: "Render farm", credits: 50000, price: 549 },
];

const spring = { type: "spring", stiffness: 380, damping: 30 } as const;
const fmt = (n: number) => n.toLocaleString("en-US");

function CoinStack({ count, active }: { count: number; active: boolean }) {
  return (
    <div aria-hidden className="flex h-20 flex-col-reverse items-center">
      {Array.from({ length: count }, (_, i) => (
        <motion.span
          key={i}
          initial={{ opacity: 0, y: -14 }}
          animate={{ opacity: 1, y: active ? -i * 1.5 : 0 }}
          transition={{ ...spring, delay: i * 0.04 }}
          className={cn(
            "-mt-1.5 h-3.5 w-14 rounded-[50%] border transition-colors duration-300",
            active ? "border-primary bg-primary/25" : "border-border bg-muted",
          )}
        />
      ))}
    </div>
  );
}

export default function PricingCreditsPacks({ packs = defaultPacks }: { packs?: Pack[] }) {
  const [selected, setSelected] = useState(2);
  const baseRate = packs[0].price / packs[0].credits;
  const pack = packs[selected];

  return (
    <section className="bg-background px-6 py-24">
      <div className="mx-auto max-w-5xl">
        <div className="mx-auto max-w-xl text-center">
          <span className="inline-flex items-center gap-1.5 rounded-full border border-border px-3 py-1 text-xs font-medium text-muted-foreground">
            <Zap className="size-3 fill-current text-amber-500" /> No subscription
          </span>
          <h2 className="mt-4 text-4xl font-semibold tracking-tight text-foreground sm:text-5xl">Buy credits. Spend them whenever.</h2>
          <p className="mt-4 text-muted-foreground">One credit renders one second of 1080p video on Lumaforge. Credits never expire.</p>
        </div>

        <div role="radiogroup" aria-label="Credit pack" className="mt-12 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
          {packs.map((p, i) => {
            const active = i === selected;
            const saving = Math.round((1 - p.price / p.credits / baseRate) * 100);
            return (
              <motion.button
                key={p.name}
                type="button"
                role="radio"
                aria-checked={active}
                onClick={() => setSelected(i)}
                initial={{ opacity: 0, y: 20 }}
                animate={{ opacity: 1, y: 0 }}
                whileTap={{ scale: 0.98 }}
                transition={{ duration: 0.5, delay: i * 0.07, ease: [0.22, 1, 0.36, 1] }}
                className="relative overflow-hidden rounded-3xl border border-border bg-card p-6 text-left outline-offset-4"
              >
                {active && (
                  <motion.span
                    layoutId="pricing-credits-packs-ring"
                    transition={spring}
                    className="absolute inset-0 rounded-3xl border-2 border-primary bg-primary/[0.06]"
                  />
                )}
                {p.best && (
                  <span className="absolute top-5 -right-10 w-36 rotate-45 bg-primary py-1 text-center text-[10px] font-semibold tracking-wider text-primary-foreground uppercase shadow-md">
                    Best value
                  </span>
                )}
                <span className="relative block">
                  <CoinStack count={i + 2} active={active} />
                  <span className="mt-5 block text-sm text-muted-foreground">{p.name}</span>
                  <span className="mt-1 block text-3xl font-semibold tracking-tight text-card-foreground tabular-nums">{fmt(p.credits)}</span>
                  <span className="block text-sm text-muted-foreground">credits</span>
                  <span className="mt-5 flex items-end justify-between border-t border-border pt-4">
                    <span className="text-xl font-semibold text-card-foreground">${p.price}</span>
                    <span className="text-right text-xs text-muted-foreground">
                      {((p.price / p.credits) * 100).toFixed(1)}¢ / credit
                      {saving > 0 && <span className="block font-medium text-emerald-600 dark:text-emerald-400">save {saving}%</span>}
                    </span>
                  </span>
                </span>
              </motion.button>
            );
          })}
        </div>

        <div className="mt-6 flex flex-wrap items-center justify-between gap-4 rounded-3xl border border-border bg-muted/50 p-4 pl-6">
          <div aria-live="polite" className="overflow-hidden text-sm text-muted-foreground">
            <AnimatePresence mode="wait" initial={false}>
              <motion.p
                key={pack.name}
                initial={{ opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -10 }}
                transition={{ duration: 0.2 }}
              >
                <span className="font-medium text-foreground">{fmt(pack.credits)} credits</span> is about{" "}
                <span className="font-medium text-foreground">{fmt(Math.round(pack.credits / 60))} minutes</span> of finished video, or{" "}
                {fmt(Math.round(pack.credits / 15))} fifteen-second ads.
              </motion.p>
            </AnimatePresence>
          </div>
          <a
            href="#"
            className="group flex h-12 items-center gap-2 rounded-full bg-primary px-6 text-sm font-medium text-primary-foreground shadow-lg shadow-primary/25 transition-transform hover:-translate-y-0.5"
          >
            Buy {pack.name} for ${pack.price}
            <ArrowRight className="size-4 transition-transform group-hover:translate-x-0.5" />
          </a>
        </div>
      </div>
    </section>
  );
}
```

## AI prompt

```text
Add the "Pricing Credits Packs" component from PixelPapa UI to this project.

What it is: Pay-as-you-go credit packs as radio cards with growing coin stacks, a best-value corner ribbon and a selection ring that glides to the chosen pack.
Category: Pricing. Depends on: lucide-react, motion.

1. Install it with the shadcn CLI:
   npx shadcn@latest add "https://pixelpapa.com/r/pricing-credits-packs.json"
   (If the CLI is unavailable, fetch that URL, write files[0].content to components/pixelpapa/pricing-credits-packs.tsx and install the listed dependencies.)
2. Import it from "@/components/pixelpapa/pricing-credits-packs" and place it where I describe below.
3. Keep its animation timings and accessibility attributes. Replace the demo copy and data with content that fits my product, and use my existing theme tokens (bg-background, text-foreground, bg-primary, border-border) rather than hard-coded colours.

Where to use it: <describe the page and position here>
```

## More pricing

- [Pricing Addon Builder](https://pixelpapa.com/components/pricing/pricing-addon-builder.md) (Pro): Build a plan by switching add-ons on and off; each one slides onto a receipt and the running total rolls to its new value.
- [Pricing Comparison Matrix](https://pixelpapa.com/components/pricing/pricing-comparison-matrix.md) (Pro): A feature-by-feature plan table with a sticky plan header, grouped rows, a column that highlights on hover and checkmarks that draw themselves in.
- [Pricing Currency Switcher](https://pixelpapa.com/components/pricing/pricing-currency-switcher.md) (Pro): Three plans in one divided panel with a hand-built currency menu; choosing a currency counts every price to its localised amount.
- [Pricing Enterprise Contact](https://pixelpapa.com/components/pricing/pricing-enterprise-contact.md) (Pro): Two self-serve plans beside an inverted enterprise card with a named contact and a short form that morphs into a confirmation.
- [Pricing Horizontal Rows](https://pixelpapa.com/components/pricing/pricing-horizontal-rows.md): An editorial price list: each plan is a full-width row with a serif name, and an inverted fill wipes up from the bottom edge on hover.
- [Pricing Single Plan Spotlight](https://pixelpapa.com/components/pricing/pricing-single-plan-spotlight.md): One plan presented large inside a slowly rotating gradient border, with a staggered included list, crossed-out extras and a money-back guarantee.
