Embedded Components
Portal & Pricing
What a customer sees when they open their License or your pricing page — CustomerPortal and its sections, the billing a customer session adds to it, the usage chrome, PricingTable, FeatureComparison, FeatureCatalog — and where a checkout takes over.
Two surfaces show a customer their commercial reality. The portal is the signed-in view: the License they are on, what it includes, how much of each quota they have used, the action to take when they need more — and, through a customer session, their subscription, their invoices, their payment method, their add-ons. The selling surface is the pricing page: every License of your public catalogue, what each grants, what it costs, and the way in, which is a checkout. Both render the same catalog and the same grants the API enforces, and every amount they show comes from the catalogue or the server, never from arithmetic in the browser. Every component here is in @kaitencloud/react-ui and needs a KaitenProvider and a KaitenStyleRoot above it.
CustomerPortal
CustomerPortal composes sections in the order of its elements prop, in a "two-column" layout — stacked on narrow containers — or "vertical". Which sections it draws by default follows the provider:
- under
mode="authenticated",CurrentPlan,IncludedFeatures,UsageMeters, andAddOns; - under
mode="customer-session", the same four, thenPaymentMethodCard,VoucherEntry, andInvoiceList— what only a customer session has anything to say in.
FlagsSummary is opt-in, because flag keys, variants, and rollout percentages are operator data, not something a customer expects in a billing portal. A section with nothing to show leaves no empty cell. Four callbacks descend to the sections — onUpgradeVersion, onAddQuota, onAddAddOn, onManageAddon — and an action is rendered only when its callback is provided. Every section is also exported on its own.
import { CustomerPortal } from "@kaitencloud/react-ui";
<CustomerPortal
layout="two-column"
onUpgradeVersion={() => navigate("/settings/license/change")}
onAddQuota={() => navigate("/settings/license/quota")}
/>| Section | Reads | Renders |
|---|---|---|
CurrentPlan | The License, the instance, the catalog — or, under a customer session, the License the portal names and its subscription | The License name, its renewal date from the instance's License term, the price the catalogue gives it, and the action row for the callbacks you passed. Under a customer session: the subscription's price and where it stands, as the portal says it — renewing on a day, in its trial, ending with its period, past due, awaiting its first payment, canceled —, a scheduled change of License, and the controls to cancel at the end of the period and to withdraw that. |
IncludedFeatures | The grants | A table of every user-facing entitlement — name, usage, limit, status. Unlimited grants fold into one expandable count line. |
UsageMeters | The grants | One bar per finite numeric quota: unit label, the current usage window when the entitlement resets periodically, and its state, read as the console reads it (below). |
AddOns | The add-ons the Instance holds, under a customer session | Each add-on with its quantity, the price of a unit, a decrease scheduled for the renewal, and Manage add-ons. The licensing snapshot of authenticated mode carries no add-ons, so the section shows its empty state there. |
PaymentMethodCard | The customer session's payment method | The labels the provider gave of it, and the provider's page to add or replace one — see Checkout. |
VoucherEntry | The customer session's subscription | A voucher code, checked with a dry run, then applied on a live subscription; the redemptions in force, never their codes. |
InvoiceList | The customer session's invoices | Each invoice, with Pay on an open one and its PDF where it has one. Absent when the portal says the invoices are not this customer's to see. |
FlagsSummary | The flags evaluated for the customer | Key, variant, rollout percentage — opt-in. |
BillingSummary | The subscription or the License term, the grants, and what you pass as billing | Standalone, not a default section: where the subscription stands — or the term, active with a renewal date, expired, not_started —, up to maxMeters usage bars (four by default), and, from your billing prop or the customer session, the amount, the interval, the payment method's labels, onManageBilling, onViewInvoices. The SDK never sees card data. |
For Sakura Tokyo on sakura-tokyo-prod, in authenticated mode, the default portal reads: Premium; Menu Items 42 of 50 — 84 % of what Premium grants, watch against the 60 its 20 % allowance lets through; Monthly Orders 730 of 1,000 with the current calendar month as its window; Delivery Drivers 8 of 10; Locations folded as one unlimited line; Delivery Tracking included; Support Tier as its configured value; the add-ons section empty. Numbers a section cannot know are not drawn: a failed usage read shows the unavailable label rather than 0 %, and a quota whose window bounds were not read is never labeled lifetime.
examples/storefront, route /billing, signed in as Sakura Tokyo (Premium) on sakura-tokyo-prod, CustomerPortal in two-column layout with the four default sections, light mode. Must show: CurrentPlan with the Premium name and the upgrade action, UsageMeters with Menu Items at 84 % in warning tone and Monthly Orders at 73 % with its current-month window line, IncludedFeatures with the folded "unlimited" line for Locations, and the empty AddOns section.Under a customer session
A customer session makes the portal the customer's own, and it changes nothing but what the customer asks for:
- Cancelling ends the subscription at the end of its period, never at once, and can be withdrawn until then. A trial ends at once, and so does a subscription still awaiting the first payment of its checkout, whose License then never starts; neither is charged, and the customer is told so before confirming.
- The add-ons change in a panel, never by a single press: Manage add-ons opens a dialog where the customer sets quantities, among the add-ons the Instance holds and those of the catalogue that fit its License. A summary says each change, its fee at the add-on's own price — never a total, which is the server's —, and when it applies: an increase at once, billed from the renewal; a decrease or a removal at the renewal, the customer keeping the current quantity until then. Confirm changes sends one change per add-on and stops at the first the API refuses, naming it and why.
- Invoices are paid on the provider's hosted page; Pay appears only on an open invoice.
- A voucher code is typed once, checked with a dry run that changes nothing, and applied on Apply: a discount from the next invoice, a boost or a feature grant at once. Every refusal of a code is the same sentence.
The limits a customer session's portal shows are the Instance's effective ones — the License's grant as add-ons, voucher boosts and entitlement exceptions leave it — and a note beside each says where it comes from, in the server's figures.
The usage chrome
Three small components live outside the portal, in your product's own layout.
EntitlementStatusBar is a single row of meters for the finite numeric quotas a customer sees: maxMeters caps how many, the rest stay in UsageMeters. Like every styled meter, it reads usage as the Kaiten console does: against the most the grant lets through — the value bought plus its allowance —, in one of six states. Healthy under 50 % of that ceiling, watch from 50 %, near limit from 80 %, in allowance once usage is past the value bought, limit reached on the ceiling — the last usage the API accepts —, and over limit past it. Near limit and in allowance are amber, the last two red, the rest green; with an allowance, the track runs to the ceiling, a hatched band covers the allowance, and a tick marks the value bought. The percentage beside a quota is of the value bought, and is not capped: 105 % inside an allowance is true. The Kaiten mark sits on the left unless hideBrand; the action on the right exists only when onUpgrade is passed, with upgradeText as its label. While loading it renders a skeleton.
These six states are the styled components' reading. The headless core keeps its own — status and percentageUsed on an entitlement, which Gate, useEntitlementCheck, and usePaywallTrigger decide on — so a host's own gates do not move with the styling.
PlanBadge renders the License name in a badge whose variant follows the License type — DEVELOPMENT, TRIAL, PAID, COMMUNITY — and null while loading or without a License. It reads the License, which a customer session does not carry: under one, it renders nothing. unstyled, it is a span carrying data-plan-type.
UpgradeButton is the intent primitive: on click it emits the kaiten:plan-changed event with intent: true, then calls onUpgrade if you passed one. label replaces the localized Upgrade; variant is the design system's button variant.
Selling: PricingTable, FeatureComparison, FeatureCatalog
The selling components read the License catalog — usePlans() in the SDK's vocabulary, or the plans prop when you pass your own — and render one column or row per License. On a public pricing page the provider runs in public mode and reads your public catalogue with a publishable key, or in static mode with a catalogue your server already read; signed in, the same components read it beside the customer's data.
PricingTable renders one card per License with its grants and its price: a number is formatted with its unit, an unlimited grant renders ∞, a BOOLEAN renders included or not. Prices come from the catalogue. A card shows the License's default monthly or annual flat fee — never a quarterly or a usage price under "/month" — and the monthly / annual toggle (showAnnualToggle) appears when a visible License has a yearly fee. A License that cannot be bought self-serve links to its selfServeCtaUrl — Contact us —, and one with no default fee shows the Custom label. onSelectPlan(slug, plan, price) fires on a self-serve card's button, with the price the card showed: its id is what a checkout buys. The default License (isDefault) is highlighted unless highlightPlan names another; hidePlans removes Licenses; the button's label is ctaText, a string or a function of the License; numberOfColumns (2, 3, or 4) sets the grid, which follows the width of its container rather than the window; showFeatureComparison appends a FeatureComparison below, with sectionLayout controlling the split; headerText, eyebrowText, and subheaderText are editorial — the catalog has no field for them. When the catalogue cannot be read — a publishable key used from an origin it does not allow, for one — the table shows an error with Try again, never an empty page.
The public catalogue lists one version per public family, its default; a catalogue composed with an organization credential lists every published version, so several versions of one family each get a column — filter the catalogue, or pass hidePlans. Selling online, Kaiten Sushi Shop's public catalogue renders Starter, free; Standard at 49.00 EUR a month, or 490.00 EUR a year with the toggle; and Premium, sold on request, whose card leads to the sales page.
examples/storefront, route /pricing, PricingTable with numberOfColumns={3} on the Kaiten Sushi Shop catalog — one card each for the versions the families serve, Starter (starter), Standard (standard), Premium (premium-v2) — showing per card the demo prices in euro cents the storefront passes, and the grants: Menu Items 5 / 10 / 50, Monthly Orders 20 / 100 / 1,000, Delivery Drivers 2 / 5 / 10, Locations 1 / 3 / ∞, Delivery Tracking excluded / excluded / included. Full width, light mode.FeatureComparison is the table: one row per entitlement, one column per License, first column sticky. A cell is the grant's value, included, unlimited, or unavailable — and a NUMBER present without an explicit cap is included, never a fabricated 0, which would be indistinguishable from a real limit of zero. Each cell carries its label for screen readers. features restricts the rows; by default they are the union of every License's grants.
FeatureCatalog is the searchable, filterable index of everything you sell: one entry per entitlement with its description, its category — the entitlement group — and the Licenses that include it. searchable and filterable add the search field and the category row; onSelectFeature(slug, feature) reacts to a click. An entitlement's icon is the namespaced token the API stores (lucide:users); the SDK ships a fixed icon set and does not resolve it — renderIcon is where you map it to your own icon library. A stage badge — ga, beta, alpha, new, coming-soon, deprecated — appears when the entitlement belongs to a group carrying one of those slugs.
From the table to a checkout
A card's button is where a checkout starts. onSelectPlan hands over the License and the price it showed; open the checkout on a page of its own — the payment provider's page sends the buyer back there — with CheckoutFlow for a signed-in customer or AnonymousCheckoutFlow for a visitor with no account. Checkout covers both, and CheckoutSummary, which shows what is due today for a selection as the server prices it.
The events your product can listen to
Commercial actions in the components emit DOM events on window, so a host reacts to an embedded action without wiring every component. Each carries a typed detail.
| Event | Emitted by |
|---|---|
kaiten:plan-changed | UpgradeButton (as an intent), CurrentPlan → Upgrade version |
kaiten:quota-added | CurrentPlan → Add quota |
kaiten:addon-added | CurrentPlan → Add add-ons, and the AddOns section |
kaiten:subscription-canceled | Declared; no component emits it |
kaiten:payment-updated | Declared; no component emits it |
Where to go next
Checkout
CheckoutFlow and AnonymousCheckoutFlow — what is due, the provider's page, and a License that applies once paid.
Gating: Paywall, Gate, Show
The other half of the same snapshot — closing a feature where the server will, and telling the customer why.
Self-serve checkout
The public catalogue the selling components read, and the customer session behind the portal.
Checkout
CheckoutFlow for a customer you know, through a customer session; AnonymousCheckoutFlow for a visitor with no account; CheckoutSummary, TrialBanner and PaymentMethodCard around them — the server prices, the payment provider's page takes the card, and a paid License applies once it is paid.
Overview
The catalog of typed rights, the grant that carries each License's value and its enforcement, and the usage measured against it — the deep-dive behind Licenses & Entitlements and Usage & Metering.