Skip to content
goodtabs UI
BrowseBrowse components

Pricing card

A plan with its price, a feature list and one action, alone or in a grid of plans.

npx shadcn@latest add https://ui.goodtabs.co/r/pricing-card.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/pricing-card

Free

For side projects and trying things out.

$0/month

  • 200 components
  • Copy any prompt
  • Community support

Pro

Popular

For people who ship every week.

$12/month

  • Everything in Free
  • Synced collections
  • Curated skill packs
  • Early access to new sets

Team

For a team that shares one library.

$48/month

  • Everything in Pro
  • Five seats
  • Shared collections
  • Priority support

Free, Pro and Team in a PricingGrid, the middle one highlighted.

Single plan

Pro

For people who ship every week.

$12/month

  • Synced collections
  • Curated skill packs
  • Early access to new sets

One card on its own with a primary action.

Yearly with a footnote

Pro

Popular

Billed once a year.

$120/year

  • Synced collections
  • Curated skill packs
  • Early access to new sets

Two months free compared with monthly

Team

Five seats, billed once a year.

$480/year

  • Everything in Pro
  • Shared collections
  • Priority support

Invoiced in USD, VAT added where it applies

period="year" and one quiet line under the action.

Usage

import { PricingCard, PricingGrid } from "@/components/ui/pricing-card"
import { Button } from "@/components/ui/button"

<PricingGrid>
  <PricingCard name="Pro" price="$12" features={["Synced collections"]} action={<Button>Start a trial</Button>} highlighted />
</PricingGrid>

Props

PropTypeDefaultDescription
namestringThe plan name.
descriptionstringOne line on who the plan is for.
pricestringThe price, already formatted, for example "$12".
periodstring"month"Billing period after the price, drawn as "/month". Pass "" for none.
featuresstring[]What the plan includes, one short line each.
actionReactNodeThe action, usually a Button. Stretched to the full width.
highlightedbooleanfalseDraws a foreground border and a small Popular badge.
footnotestringOne quiet line under the action.
columns2 | 33On PricingGrid: plans side by side from md up.

Source

pricing-card/pricing-card.tsx
import * as React from "react";
import { CheckIcon } from "lucide-react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { cn } from "@/lib/utils";

type PricingCardProps = Omit<React.ComponentProps<typeof Card>, "children"> & {
  /** The plan name, for example "Pro". */
  name: string;
  /** One line on who the plan is for. */
  description?: string;
  /** The price, already formatted, for example "$12" or "Free". */
  price: string;
  /** Billing period after the price, drawn as a mono "/month". Pass an empty string for none. */
  period?: string;
  /** What the plan includes, one short line each. */
  features?: string[];
  /** The action, usually a Button. It is stretched to the full width. */
  action?: React.ReactNode;
  /** Draws a foreground border and a small Popular badge. */
  highlighted?: boolean;
  /** One quiet line under the action, for example the billing terms. */
  footnote?: string;
};

/**
 * A plan: name, price, features and one action. Monochrome; the highlighted plan
 * gets a stronger border and a badge, not a colour.
 */
export function PricingCard({
  name,
  description,
  price,
  period = "month",
  features,
  action,
  highlighted = false,
  footnote,
  className,
  ...props
}: PricingCardProps) {
  return (
    <Card
      data-slot="pricing-card"
      data-highlighted={highlighted || undefined}
      className={cn("surface-shine gap-5 rounded-lg", highlighted && "ring-foreground", className)}
      {...props}
    >
      <CardHeader className="gap-1">
        <div className="flex items-center justify-between gap-3">
          <h3 className="m-0 text-sm leading-5 font-medium text-foreground">{name}</h3>
          {highlighted && (
            <span className="inline-flex h-5 shrink-0 items-center rounded-sm bg-muted px-1.5 font-mono text-xs text-foreground">
              Popular
            </span>
          )}
        </div>
        {description && <p className="m-0 text-sm text-muted-foreground">{description}</p>}
      </CardHeader>
      <CardContent className="flex flex-col gap-5">
        <p className="m-0 flex items-baseline gap-1">
          <span className="text-3xl leading-none font-medium tracking-tight tabular-nums">{price}</span>
          {period && <span className="font-mono text-xs text-muted-foreground">/{period}</span>}
        </p>
        {features && features.length > 0 && (
          <ul className="m-0 flex list-none flex-col gap-2 p-0">
            {features.map((feature) => (
              <li key={feature} className="flex items-start gap-2 text-sm text-foreground/85">
                <CheckIcon className="mt-0.5 size-4 shrink-0 text-muted-foreground" aria-hidden="true" />
                <span>{feature}</span>
              </li>
            ))}
          </ul>
        )}
      </CardContent>
      {(action || footnote) && (
        <CardContent className="mt-auto flex flex-col gap-3">
          {action && <div className="flex flex-col *:w-full">{action}</div>}
          {footnote && <p className="m-0 text-center text-xs text-muted-foreground">{footnote}</p>}
        </CardContent>
      )}
    </Card>
  );
}

type PricingGridProps = React.ComponentProps<"div"> & {
  /** Plans side by side from md up. One column below. */
  columns?: 2 | 3;
};

const COLUMNS = { 2: "md:grid-cols-2", 3: "md:grid-cols-3" } as const;

/** Two or three plans side by side with equal heights. */
export function PricingGrid({ columns = 3, className, ...props }: PricingGridProps) {
  return <div data-slot="pricing-grid" className={cn("grid w-full gap-4", COLUMNS[columns], className)} {...props} />;
}

For AI agents

Hand your agent this page, the markdown at /components/pricing-card/prompt.md, or the Copy AI prompt button above.

When to use

  • A pricing page or section with two or three plans side by side
  • One plan on its own, for example an upgrade prompt inside the app
  • Yearly and monthly variants of the same plans, switched by a toggle above

When not to use

  • More than four plans or many feature rows, which want a comparison table
  • Usage-based pricing with sliders and calculators
  • A plan with no action to take

Agent notes

  • Format the price before passing it; the card adds only the mono period after it
  • The action is any element and is stretched to full width; pass a shadcn Button and pick its variant
  • highlighted is monochrome by design: a foreground ring and a muted Popular badge, no accent colour
  • Server component with no state; put the monthly and yearly toggle outside and swap the props