Plan comparison
BlockPlans side by side with prices and feature lists.
pnpm dlx shadcn@latest add https://caliperui.site/r/plan-comparison.jsonSimple pricing
Pay per seat. Change plans or cancel at any time.
Prices in USD. Taxes added at checkout.
When to use #
Use it for
- A pricing page with a few tiers.
Skip it when
- There is a single plan.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/plan-comparison.json
Adds its 8 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
example.tsx
import { useState } from "react" import { PlanComparison } from "@/components/ui/plan-comparison" const plans = [ { id: "starter", name: "Starter", description: "For trying things out.", price: { monthly: 0, yearly: 0 }, features: ["40 components", "Community support", "Personal projects"], cta: "Start free" }, { id: "team", name: "Team", description: "For shipping a product.", price: { monthly: 24, yearly: 19 }, features: ["Every component and block", "Private repository access", "Email support", "Unlimited projects"], cta: "Choose Team", highlighted: true, badge: "Most popular" }, { id: "business", name: "Business", description: "For admin controls.", price: { monthly: 48, yearly: 40 }, features: ["Everything in Team", "Priority support", "Seat management", "Custom license terms"], cta: "Choose Business" }, { id: "enterprise", name: "Enterprise", description: "For custom needs.", price: "Talk to us", features: ["Everything in Business", "Security review", "Dedicated contact"], cta: "Contact sales" }, ]; export function PlanComparisonDemo() { const [, setLog] = useState(""); return ( <PlanComparison plans={plans} description="Pay per seat. Change plans or cancel at any time." onSelect={(id, billing) => setLog(`Selected ${id} (${billing})`)} footer="Prices in USD. Taxes added at checkout." /> ); }
API reference #
PlanComparison
component
PropTypeDefault
plans *readonly Plan[]—
titlestring"Simple pricing"
descriptionstring—
currencystring"USD"
onSelect((planId: string, billing: "monthly" | "yearly") => void)—
footerReactNode—
classNamestring—
Accessibility #
It is built on native elements and keeps aria-label and aria-hidden in step with state.
No custom key handling; Tab and the native activation keys do the work.
Motion #
Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.