> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kerne.io/llms.txt
> Use this file to discover all available pages before exploring further.

# <PricingTable>

> A real pricing page, built from your live catalog

Different job from [`<SubscriptionCard>`](/sdks/client/react/ui/account): that one is a settings control for someone who already has an account ("here's what you can move to"). `<PricingTable>` is the page that sells - plan cards, a monthly/annual toggle, and a checkout button, built from whatever you've configured in **Catalog**.

```tsx theme={"system"}
import { PricingTable } from '@kerne/react/ui';

<PricingTable defaultLabel="annual">
  <PricingTable.LabelSwitch />
  <PricingTable.Plans featuredSlug="pro" featuredBadge="Most popular" />
</PricingTable>;
```

That's a complete pricing page: the toggle only appears if you actually sell more than one price label, and switching it animates the price rather than swapping it instantly.

Clicking a plan checks out a new subscriber directly. For someone already subscribed, it opens a confirmation panel first, then swaps the plan in place (`changePlanPrice()`) instead of starting a second checkout — Stripe have no notion of "replace my existing subscription", so a fresh checkout session there would create a second, independent one rather than replacing the first. `onSelectPlan` overrides this whole flow, confirmation included, when you want your own.

## Building your own layout

`<PricingTable.Plans>` is a shortcut in a responsive grid. Skip it and place `<PricingTable.Plan>` yourself for a custom layout - a plan featured in the middle, a two-column comparison, a page that sells just one plan:

```tsx theme={"system"}
<PricingTable>
  <PricingTable.LabelSwitch />
  <div className="my-three-column-layout">
    <PricingTable.Plan slug="starter" />
    <PricingTable.Plan slug="pro" featured badge="Most popular" />
    <PricingTable.Plan slug="business" />
  </div>
</PricingTable>
```

`featured` and `badge` are yours to set - neither is inferred from the plan itself. A plan's `is_default` means "the free tier a signup falls back to with no subscription," not "recommended," so nothing here derives a badge from data.

## Reference

| Prop           | On                                            | What it does                                                                                                                |
| -------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `product`      | `<PricingTable>`                              | Which product's plans to show. Omit for your default product.                                                               |
| `defaultLabel` | `<PricingTable>`                              | Price label selected on first render (e.g. `"annual"`). Falls back to the first one if it doesn't exist.                    |
| `onSelectPlan` | `<PricingTable>`                              | Override the built-in checkout-or-switch flow - e.g. to show your own confirmation step first. Called with `(plan, price)`. |
| `slug`         | `<PricingTable.Plan>`                         | Which plan to render.                                                                                                       |
| `maxFeatures`  | `<PricingTable.Plan>`, `<PricingTable.Plans>` | Cap the entitlement list before a "+N more" line.                                                                           |

## Going fully custom

`usePricingTable()` gives you the same state the components above read, for a page that doesn't use them at all:

```tsx theme={"system"}
import { usePricingTable } from '@kerne/react/ui';

function CustomPlanList() {
  const { plans, selectedLabelSlug, onSelectPlan } = usePricingTable();
  return plans.map((plan) => (
    <MyOwnCard key={plan.id} plan={plan} onClick={() => onSelectPlan(plan)} />
  ));
}
```

Must be called somewhere under `<PricingTable>` - it reads the same provider `<PricingTable.LabelSwitch>`/`<PricingTable.Plan>` do.

Formatting a price is more than `${amount}`: a label's `display_interval` needs converting (an annual price shown at its monthly cost), and the amount needs locale-aware currency formatting. Reuse the same helpers the built-in card uses instead of redoing that arithmetic:

```tsx theme={"system"}
import { formatMoney, formatInterval, resolvePriceDisplay, useLocalization } from '@kerne/react/ui';

const l10n = useLocalization();
const price = plan.prices.find((p) => p.label.slug === selectedLabelSlug) ?? plan.prices[0];
const { displayAmount, displayInterval } = resolvePriceDisplay(price, price.label.display_interval);

`${formatMoney(displayAmount, price.currency)} / ${formatInterval(displayInterval, l10n.billing)}`; // "€24 / month"
```

`formatInterval`, `formatDate`, `formatNumber`, `humanizeFeatureKey`, `collectPriceLabels` and `resolveLabelSavings` (the "Save 20%" hint on a label switch) are exported from `@kerne/react/ui` the same way.
