Pricing
A responsive marketing pricing table. Named parts you assemble into plan cards: a name, a big tabular price, a feature checklist and a bottom-pinned call to action. Mark the recommended plan featured.
Installation#
Usage#
import {
Pricing,
PricingTier,
PricingTierHeader,
PricingTierName,
PricingTierDescription,
PricingPrice,
PricingAmount,
PricingPeriod,
PricingFeatures,
PricingFeature,
PricingTierAction,
} from "@/components/ui/pricing"
import { Button } from "@/components/ui/button"
<Pricing columns={3}>
<PricingTier featured>
<PricingTierHeader>
<PricingTierName>Pro</PricingTierName>
<PricingTierDescription>For teams shipping in production.</PricingTierDescription>
</PricingTierHeader>
<PricingPrice>
<PricingAmount>$29</PricingAmount>
<PricingPeriod>/month</PricingPeriod>
</PricingPrice>
<PricingFeatures>
<PricingFeature>Unlimited projects</PricingFeature>
<PricingFeature included={false}>SSO & SAML</PricingFeature>
</PricingFeatures>
<PricingTierAction>
<Button>Start free trial</Button>
</PricingTierAction>
</PricingTier>
</Pricing>Card styles#
The tone prop on PricingTier sets the card's surface, and is orthogonal to featured (the recommended ring), so the two combine. default is the bare card (a transparent fill, so it blends with whatever it sits on, delineated by its border alone); outline swaps the hairline for a brand edge; solid fills the card with the accent and flips every part to inverse text and glyphs (pair it with a light CTA and a light badge); muted sits the card on a soft grey panel; dashed draws the bare card on a dashed contour, for an open-ended plan (a custom quote, a service) beside fixed-price tiers. The CTA's Button variant stays your choice.
Banner#
Wrap a tier in PricingTierBanner for the banner-behind treatment: the card nests inside a brand panel that peeks as a top label and a thin accent edge all around. It composes over any tone and, inside a grid, lines up with un-bannered neighbours.
Action position#
actionPosition sets where the CTA sits. bottom (default) pins it to the card foot via mt-auto, so CTAs line up across cards no matter how long each list is. top lets it sit in flow right under the price, with the checklist below: place PricingTierAction accordingly in the markup.
Feature states#
Every PricingFeature is one of three states. Included rows (the default) get a filled success check. Mark a row included={false} for a filled cross and a dimmed label, so a single list reads as have vs. have-not. Add a hint to either to append an info glyph whose tooltip carries the caveat or limit, keeping the label terse.
Features with icons#
For a lifetime or all-in bundle, the value isn't a have vs. have-not checklist, it's a long, categorised inventory. Swap the flat PricingFeatures for stacked PricingFeatureGroup sections: each is a centered Divider header (with an optional icon) over its own list, and every PricingFeature carries a contextual icon that reads neutral (the success-check tint stays reserved for the default checklist). The rows sit at full text strength because the icon, not a dimmed weight, marks the line, and any row can wrap a link. Here the CTA floats under the price with actionPosition="top" so the inventory reads beneath it.
Size#
size sets a tier's type scale. md (default) is the compact card for a docs page, a dialog or a three-up row. lg is the landing-page scale, where a long categorised inventory is read at body size: 16px rows, a 48px price, a 20px name and a readable group label. The name, price, rows and group labels step together, so the card keeps its hierarchy at either size.
Billing cycle#
The component is stateless: you own the prices, so a billing toggle is a few lines of state that swap the PricingAmount and PricingPeriod. Because the amount is tabular-nums, the digits change without the row reflowing. Here a Tabs control drives a columns={2} table, and a small RollingAmount cross-fades and rolls the price so the swap reads as smooth instead of a snap.
Build your plan#
Because the component is stateless, the price is just a number you own, so you can drive it from anything, including a set of add-on checkboxes. Tick a box and the total counts up to the new figure instead of snapping: a small useCountUp hook tweens it on requestAnimationFrame (interruptible, and it collapses to an instant settle under prefers-reduced-motion). Each add-on is a full-row label so the whole row is the hit target; the selected row keeps a transparent background, with the brand-coloured price keying off the checkbox's data-state via group-has, so there's no extra state to thread.
API reference#
Pricing
The grid container. columns is 2, 3 (default), or 4 and sets how many tiers sit in a row at the lg breakpoint; below that the grid collapses to two columns, then one. Forwards native <div> props and className.
PricingTier
One plan card. tone is default (transparent fill), outline (brand edge), solid (accent fill, inverse parts), or muted (grey panel). actionPosition is bottom (default) or top. size is md (default) or lg, the landing-page type scale. Pass featured to anchor the recommended plan with a brand ring and elevated shadow (it also sets data-featured); it is orthogonal to tone. Pass asChild to render the whole card as a link.
PricingTierHeader · PricingTierName · PricingTierDescription
The header block, the balanced <h3> plan name, and the muted <p> description. Drop a Badge next to the name (in a flex row) to label a featured plan.
PricingPrice · PricingAmount · PricingPeriod
The baseline-aligned price row, the large headline PricingAmount (tabular-nums, so swapping plans never shifts the layout), and the muted PricingPeriod suffix (e.g. /month). The amount accepts text too, e.g. Custom.
PricingFeatures · PricingFeature
The checklist <ul> and its rows. Each PricingFeature renders its own leading glyph: a filled success check when included (default), or a filled cross with a dimmed label when included={false}. Override the glyph with the icon prop. Pass hint (any node) to append a trailing info glyph whose tooltip carries the detail on hover or focus; hintLabel sets its accessible label (defaults to "More information").
PricingFeatureGroup
A labelled section of features for the "features with icons" layout: a centered Divider header (its label, plus an optional leading icon) over a <ul> of PricingFeature rows. Stack several inside a tier in place of a single flat PricingFeatures when the value is a long, categorised inventory rather than a have/have-not checklist. Rows in a group read at full text strength, and each one's contextual icon renders neutral (the success-check tint is reserved for the default checklist).
PricingTierAction
The CTA wrapper. By default it is bottom-pinned (mt-auto), so buttons line up across cards no matter how long each feature list is; set the tier's actionPosition="top" and place it under the price to float it instead. Its direct child is stretched to the full card width: drop a Koala Button inside (primary for the featured plan, outline for the rest).
PricingTierBanner
An optional wrapper around a PricingTier for the banner-behind treatment: the card nests inside a brand panel that peeks as a top label (the only required prop) and a thin accent edge. Composes over any tone and stretches to the row height inside a grid.