Pre-alphaThe registry and the CLI are not published yet.Roadmap
Compositions

Pricing Card

Plan name, price and period, features, and a button, with featured and current states.

Plans

Free
$0
For trying things out.
3 projectsCommunity support
Current plan
ProMost popular
$12/month
For people who ship every week.
Unlimited projectsPriority supportCustom domains
Start free trial

The featured plan has a 2pt border in the primary color and the only solid button. The current plan’s button is disabled.

Choose a plan

YearlySave 30%$8.25 a month$99/year
MonthlyCancel anytime$12/month

One plan per row, picked with a radio. It fits where a full card is too tall: a bottom sheet, a checkout step, a settings screen.

Plans

MonthlyYearly
ProMost popular
$9.60/month$12-20%
$115 billed yearlyFor people who ship every week.
Unlimited projectsPriority support
Choose plan

A segmented control switches the price between monthly and yearly billing. On yearly, the monthly price is struck through next to the discount.

Plans

ProMost popular
$12/month
For people who ship every week.
Unlimited projectsVersion historyPriority support
Start free trial
Team
$32/month
Shared workspaces and roles.
Everything in ProUp to 10 members
Choose plan

The plan you push sits on the primary color, with the text and the button inverted. The other plans stay as outlined Default cards.

Plans

ProPopular
For people who ship every week.
Unlimited projectsPriority support
$12/monthChoose plan
Free
3 projects
$0Current plan

The plan on the left, the price and the button on the right. Made for tablets and landscape, where a stacked card wastes the width.

Go Premium

Save 60%Yearly
$3.33/month$39.99 billed yearly7-day free trial
Monthly
$7.99/monthCancel anytime

Large paywall tiles, App Store style, side by side in a horizontal rail. The whole tile is the radio.

The variants are separate components with their own code: install the one you need.

Installation

npx axiom add pricing-card
pnpm dlx axiom add pricing-card
yarn dlx axiom add pricing-card
bun x axiom add pricing-card

Also copies: card, badge, button, icon, title, text and their dependencies. The file lands in components/compositions/pricing-card.tsx. It brings no tokens of its own.

Requires the check icon in your icon registry.

npx axiom add pricing-card-compact
pnpm dlx axiom add pricing-card-compact
yarn dlx axiom add pricing-card-compact
bun x axiom add pricing-card-compact

Also copies: radio, badge, title, text and their dependencies. The file lands in components/compositions/pricing-card-compact.tsx. It brings no tokens of its own.

npx axiom add pricing-card-toggle
pnpm dlx axiom add pricing-card-toggle
yarn dlx axiom add pricing-card-toggle
bun x axiom add pricing-card-toggle

Also copies: card, segmented-control, badge, button, icon, title, text and their dependencies. The file lands in components/compositions/pricing-card-toggle.tsx. It brings no tokens of its own.

Requires the check icon in your icon registry.

npx axiom add pricing-card-hero
pnpm dlx axiom add pricing-card-hero
yarn dlx axiom add pricing-card-hero
bun x axiom add pricing-card-hero

Also copies: card, badge, button, icon, title, text and their dependencies. The file lands in components/compositions/pricing-card-hero.tsx. It brings no tokens of its own: the surface is primary.default and the text primary.on.

Requires the check icon in your icon registry.

npx axiom add pricing-card-horizontal
pnpm dlx axiom add pricing-card-horizontal
yarn dlx axiom add pricing-card-horizontal
bun x axiom add pricing-card-horizontal

Also copies: card, badge, button, icon, title, text and their dependencies. The file lands in components/compositions/pricing-card-horizontal.tsx. It brings no tokens of its own.

Requires the check icon in your icon registry.

npx axiom add pricing-card-tile
pnpm dlx axiom add pricing-card-tile
yarn dlx axiom add pricing-card-tile
bun x axiom add pricing-card-tile

Also copies: radio, badge, title, text and their dependencies. The file lands in components/compositions/pricing-card-tile.tsx. It brings no tokens of its own.

Usage

import { PricingCard } from "@/components/compositions/pricing-card";

{plans.map((plan) => (
	<PricingCard
		key={plan.id}
		name={plan.name}
		price={formatPrice(plan.price)}
		period={plan.price > 0 ? "/month" : undefined}
		description={plan.tagline}
		features={plan.features}
		badge={plan.id === "pro" ? "Most popular" : undefined}
		featured={plan.id === "pro"}
		current={plan.id === subscription.planId}
		onAction={() => purchase(plan.id)}
	/>
))}

Feature one plan at most: the border and the solid button only work as a signal when they’re alone.

import { PricingCardCompact } from "@/components/compositions/pricing-card-compact";
import { RadioGroup } from "@/components/ui/radio";

<RadioGroup value={plan} onValueChange={setPlan} gap={2} accessibilityLabel="Plan">
	<PricingCardCompact value="yearly" name="Yearly" price="$99" period="/year" description="$8.25 a month" badge="Save 30%" />
	<PricingCardCompact value="monthly" name="Monthly" price="$12" period="/month" description="Cancel anytime" />
</RadioGroup>;

Each row is a Radio: it must sit inside a RadioGroup, which holds the selected plan. Put the purchase button under the group.

import { PricingCardToggle, type Billing } from "@/components/compositions/pricing-card-toggle";

const [billing, setBilling] = useState<Billing>("yearly");

<PricingCardToggle
	name="Pro"
	monthlyPrice="$12"
	yearlyPrice="$9.60"
	discount="-20%"
	yearlyCaption="$115 billed yearly"
	features={["Unlimited projects", "Priority support"]}
	featured
	billing={billing}
	onBillingChange={setBilling}
	onAction={(choice) => purchase("pro", choice)}
/>;

Both prices are for the same period (/month), so the struck price compares like with like. With several cards, pass the same billing to all of them: switching one switches the others.

import { PricingCard } from "@/components/compositions/pricing-card";
import { PricingCardHero } from "@/components/compositions/pricing-card-hero";

<PricingCardHero
	name="Pro"
	price="$12"
	period="/month"
	badge="Most popular"
	features={["Unlimited projects", "Priority support"]}
	actionLabel="Start free trial"
	onAction={() => purchase("pro")}
/>
<PricingCard name="Team" price="$32" period="/month" onAction={() => purchase("team")} />;

Use it once per screen. Two hero cards cancel each other out.

import { PricingCardHorizontal } from "@/components/compositions/pricing-card-horizontal";

<PricingCardHorizontal
	name="Pro"
	price="$12"
	period="/month"
	description="For people who ship every week."
	features={["Unlimited projects", "Priority support"]}
	featured
	current={subscription.planId === "pro"}
	onAction={() => purchase("pro")}
/>;

The features wrap on several columns. On a phone in portrait, switch to the default card: below about 400pt, the plan column gets too narrow.

import { ScrollView } from "react-native";
import { PricingCardTile } from "@/components/compositions/pricing-card-tile";
import { RadioGroup } from "@/components/ui/radio";

<RadioGroup value={plan} onValueChange={setPlan} accessibilityLabel="Plan">
	<ScrollView
		horizontal
		showsHorizontalScrollIndicator={false}
		contentContainerStyle={{ gap: 12, paddingHorizontal: 16, paddingEnd: 16 + width / 2 }}
	>
		<PricingCardTile
			value="yearly"
			name="Yearly"
			price="$3.33"
			period="/month"
			description="$39.99 billed yearly"
			trial="7-day free trial"
			badge="Save 60%"
			style={{ width: 168 }}
		/>
		<PricingCardTile value="monthly" name="Monthly" price="$7.99" period="/month" style={{ width: 168 }} />
	</ScrollView>
</RadioGroup>;

Give each tile a width. Tiles side by side share the height of the tallest one. Leave room after the last tile (half the screen width) so the rail never stops on a wall.

Props

PropTypeDefaultDescription
namestring—Name of the plan.
pricestring—Already formatted, in large type: "$12".
periodstring—After the price, muted: "/month".
descriptionstring—Who the plan is for, under the price.
featuresstring[]—Short features, each after a check.
badgestring—Tag next to the name. Highlight color on the featured plan, neutral otherwise.
featuredbooleanfalsePrimary border and solid button.
currentbooleanfalseThe user’s plan: the button is disabled and reads currentLabel.
actionLabelstring"Choose plan"Label of the button.
currentLabelstring"Current plan"Label of the button on the current plan.
onAction() => void—Shows the full-width button.
styleStyleProp<ViewStyle>—Extra styles for the card.
PropTypeDefaultDescription
valuestring—Given to the RadioGroup when this plan is picked.
namestring—Name of the plan.
pricestring—Already formatted, on the right: "$99".
periodstring—Under the price: "/year".
descriptionstring—Under the name: "$8.25 a month".
badgestring—Highlight tag next to the name: "Save 30%".
disabledbooleanfalseDims the row and ignores presses.
styleStyleProp<ViewStyle>—Extra styles for the row.
PropTypeDefaultDescription
namestring—Name of the plan.
monthlyPricestring—Already formatted: "$12". Struck through when yearly is shown.
yearlyPricestring—Same period, billed yearly: "$9.60".
periodstring"/month"After both prices.
discountstring—Success badge next to the yearly price: "-20%".
yearlyCaptionstring—Under the yearly price: "$115 billed yearly".
descriptionstring—Who the plan is for.
featuresstring[]—Short features, each after a check.
badgestring—Tag next to the name.
featuredbooleanfalsePrimary border and solid button.
billing'monthly' | 'yearly'—Controlled billing, to share one choice across several cards.
defaultBilling'monthly' | 'yearly''monthly'Starting billing when uncontrolled.
onBillingChange(billing: Billing) => void—Called when the segmented control changes.
monthlyLabelstring"Monthly"Label of the first segment.
yearlyLabelstring"Yearly"Label of the second segment.
actionLabelstring"Choose plan"Label of the button.
onAction(billing: Billing) => void—Shows the full-width button. Receives the billing shown when pressed.
styleStyleProp<ViewStyle>—Extra styles for the card.
PropTypeDefaultDescription
namestring—Name of the plan.
pricestring—Already formatted, in display type: "$12".
periodstring—After the price.
descriptionstring—Who the plan is for.
featuresstring[]—Short features, each after a check.
badgestring—Highlight tag next to the name.
actionLabelstring"Choose plan"Label of the button.
onAction() => void—Shows the full-width inverted button.
styleStyleProp<ViewStyle>—Extra styles for the card.
PropTypeDefaultDescription
namestring—Name of the plan.
pricestring—Already formatted, on the right.
periodstring—Under the price.
descriptionstring—Who the plan is for.
featuresstring[]—Short features, wrapping on several columns.
badgestring—Tag next to the name.
featuredbooleanfalsePrimary border and solid button.
currentbooleanfalseThe user’s plan: the button is disabled and reads currentLabel.
actionLabelstring"Choose plan"Label of the button.
currentLabelstring"Current plan"Label of the button on the current plan.
onAction() => void—Shows the button under the price.
styleStyleProp<ViewStyle>—Extra styles for the card.
PropTypeDefaultDescription
valuestring—Given to the RadioGroup when this plan is picked.
namestring—Name of the plan.
pricestring—Already formatted, in large type: "$3.33".
periodstring—After the price: "/month".
descriptionstring—Under the price, muted: "$39.99 billed yearly".
trialstring—Last line, semibold: "7-day free trial".
badgestring—Highlight tag above the name: "Save 60%".
disabledbooleanfalseDims the tile and ignores presses.
styleStyleProp<ViewStyle>—Give it a width.

Accessibility

The price and its period are read together: “$12 /month”. Buttons are labelled with the plan: “Start free trial, Pro”, or “Current plan, Free” while disabled.

Compact and Tile are radios in a radiogroup: each is read as one item with its state, “Yearly, $99 /year, $8.25 a month, Save 30%, selected”. On Toggle, the yearly price is read with what it replaces: “$9.60 /month, instead of $12, -20%”.

Built with

  • Card: the outlined surface, filled with the primary color on Hero.
  • Title: the name and the price.
  • Badge: the plan tag and the discount.
  • Icon and Text: the features.
  • Button: solid on the featured plan, outlined on the others, inverted on Hero.
  • Radio: the selection of Compact and Tile.
  • Segmented Control: the billing switch of Toggle.

Used in

No block uses Pricing Card yet.