MKT UI
Sections

Package comparison

Two or three tiers side by side, each with its own call to action.

Open preview (opens in a new tab)

Installation

npx shadcn@latest add http://localhost:3000/r/pricing-packages.json

MKT UI is not published yet, so this points at your local development server. Set NEXT_PUBLIC_REGISTRY_URL to the deployed origin to show the public command.

Also installs: container, section, section-heading, package-card, inclusion-list.

Usage

import { Button } from "@/components/ui/button";
import { PriceDisplay } from "@/components/price-display";
import { PricingPackages } from "@/components/pricing-packages";

<PricingPackages
  title="Straightforward pricing"
  packages={[
    {
      id: "callout",
      name: "Callout",
      description: "One visit, one problem.",
      price: <PriceDisplay prefix="From" currency="$" amount="149" period="visit" align="center" />,
      inclusions: ["First hour on site", { label: "Out-of-hours cover", included: false }],
      action: <Button variant="outline">Book a visit</Button>,
    },
    {
      id: "care",
      name: "Care plan",
      featured: true,
      price: <PriceDisplay currency="$" amount="29" period="per month" align="center" />,
      inclusions: ["Everything in Callout", "Annual system service"],
      action: <Button>Join the plan</Button>,
      note: "Cancel any time",
    },
  ]}
  footnote="Prices exclude parts and materials unless stated."
/>

Props

PropTypeDefaultDescription
title (required)ReactNode—Section heading.
packages (required)PricingPackage[]—Two or three tiers. More than three stops being comparable.
columns2 | 33Columns on wide screens.
toggleReactNode—Control above the grid, e.g. a monthly/yearly switch. Supply your own client component.
footnoteReactNode—Tax treatment, contract length, what is excluded.
align"start" | "center""center"Heading alignment.
surface"default" | "muted" | "brand" | "inverted""default"Background treatment.

PricingPackage

PropTypeDefaultDescription
id (required)string—Stable key.
name (required)ReactNode—Tier name.
descriptionReactNode—One sentence on who this tier suits. Stops people guessing.
priceReactNode—A `PriceDisplay`, or plain text such as "Custom quote".
inclusions(InclusionItem | string)[]—What is included. Pass `{ included: false }` to show an exclusion.
actionReactNode—The button. Every tier needs one, including the custom-quote tier.
featuredboolean—Promotes the tier and shows a label.
featuredLabelReactNode"Most popular"Text of the promotion label.

Behaviour

The featured tier is labelled, not just styled. Signalling "recommended" through a border alone leaves the recommendation invisible to anyone who cannot see the border, and it is the most load-bearing piece of information on a pricing page.

Exclusions are shown, not omitted. "No phone support" is something a buyer needs to see before they pay. Excluded lines are struck through and carry a screen-reader-only "Not included", so the distinction does not depend on noticing a different icon.

Every tier gets an action. A column a visitor cannot act on is a dead end, and it is usually the column with the largest deal in it.

Billing toggles

The section renders on the server, so a monthly/yearly toggle is your own client component passed through toggle. A static pricing page then ships no JavaScript.

Keep switched values consistent

If you add a toggle, make sure every tier's price, period, and footnote all update together. A page showing yearly prices beside a "per month" label is the most common pricing bug there is.

Limitations

  • No feature-matrix table variant yet.
  • No add-ons, usage pricing, or per-seat calculator.
  • No built-in billing toggle — see above.

On this page