Kaiten

Embedded Components

Embedded Components

React components that render, inside your product and on your site, what Kaiten knows — a customer's License, quotas and flags, your public catalogue, the checkout that sells a License, and the portal a customer manages it from. Headless core, styled components, four provider modes, one appearance system, one browser boundary.

Embedded components are the part of Kaiten your customers see. Inside your product, they render what the control plane knows about the customer who is signed in: the License they are on, how much of each quota they have used, which flags are on for them, their subscription and invoices, and — when a quota closes — the commercial action to propose. On your site, they render your public catalogue and the checkout that sells a License, to a customer you know or to a visitor with no account. A paywall over a locked feature, a usage bar in the header, a pricing table, a checkout, a billing page in your product's settings: each is a component reading the same contract the API enforces.

The components decide nothing. The server is the authority on every limit and every amount; a component anticipates the decision the API is about to make and errs on the safe side — a gate stays open while the data loads, a paywall closes on a failed read, a usage report without a key is never retried, and a total is never added up in the browser. This page is the model; gating, checkout, and portal & pricing cover the components themselves.

Two packages

PackageWhat it holdsWhat it renders
@kaitencloud/react — the headless coreKaitenProvider and KaitenScopeProvider, the hooks (useEntitlementCheck, usePaywallTrigger, useEntitlements, usePlans, usePortal, useReportUsage, useFeatureFlag, …), and six control primitives: Gate, Show, KaitenLoaded, KaitenLoading, KaitenFailed, KaitenErrorBoundary. Its own entry, @kaitencloud/react/billing, holds the billing hooks — useCheckout, useAnonymousCheckout, useInvoices, useCancelSubscription, … — so a host that sells nothing bundles none of themOnly what you pass it. No DOM of its own, no stylesheet, no styling dependency — the smallest bundle, and the stable surface.
@kaitencloud/react-ui — the styled componentsThe twenty-two components — gating, pricing, the checkouts, the portal and its billing sections, usage chrome, KaitenBranding — plus KaitenStyleRoot, the appearance system, and the stylesheet. Re-exports the whole core, so a styled app imports from one place.Kaiten's design, scoped to its own subtree, themed through appearance, or bare semantic HTML with unstyled.

An app that only gates its own buttons installs the core and nothing else. An app that wants the pricing table, the checkout and the portal installs react-ui and gets the core with it. Both are on npm at 1.0 and version independently. The core's exports, the billing hooks included, follow semantic versioning, and so does each component's unstyled rendering; the styled rendering, KaitenStyleRoot, the appearance API and the stylesheet are experimental and may change in a minor release — pin an exact version if you build on them. The full list of hooks and props lives in the repository and its Storybook — kaitencloud/sdk-js.

One provider, one scope

KaitenProvider is the only data entry point. It reads one scope — a customer's licensing data, a catalogue, or both — and every hook and component projects a part of it. There is no read per entitlement and no read per flag; the data is refreshed on a polling schedule, on an explicit refetch, after a change made through a billing hook, and optimistically after a usage report.

A provider has a mode:

modeReadsCredential in the browserFor
customer-sessionOne Customer's portal, through a customer session your backend mints: the subscription, the effective entitlements, the add-ons, the invoices, the payment method — and, given a publishable key, the catalogue beside itThe session's kst_, renewed through your own endpointYour customers' signed-in product: gating, the portal, the checkout
authenticatedThe licensing snapshot of one customerId and instanceId: the License, every grant with its usage, the flags evaluated for that customerA credential over your whole organization — a JWT your gateway trusts — or none, when you pass a client of your own that talks to your backendA surface whose users are members of your organization, or that goes through your backend
publicThe public catalogue, with a publishable key (pk_…); given an anonymousCheckoutClient, a visitor's checkoutThe publishable keyThe pricing page, and the anonymous checkout
staticThe catalogue you pass as catalog — no client, no request, no pollingNoneA pricing page rendered from data your server already holds

Gating needs a customer, so it belongs under customer-session or authenticated; under public or static, Gate and Paywall stay open and say so in development. A customer session carries no flags and takes no usage report: report usage from your backend. Polling is on by default in the two customer modes, every five minutes; a public provider polls only when asked.

A provider's scope is its mode and what it reads with — the key, the session client, the customer and the instance. Two providers with the same scope share one fetch and one cache; a nested provider with a different scope is an independent root. KaitenScopeProvider switches the scope when a user signs in: it renders the public catalogue while no one is, then the customer's portal through a customer session, and remounts the SDK across that boundary so that a client and its cache never outlive the scope they were made for.

Set apiUrl explicitly. The default is /api on the current origin, which sends every request to your own backend — whose 404 surfaces as KaitenError: Unknown error. Your organization's API base URL is the value the quickstart exports as $KAITEN_API.

The browser boundary

Nothing that authenticates as your organization belongs in a browser bundle. The SDK hub describes the boundary the packages enforce; for the components it means three things. A ksh_ or ksm_ credential passed to the provider is refused at request time in a browser runtime. Your customers' browsers hold credentials made for them — a publishable key for the catalogue and the start of an anonymous checkout, a checkout session's token for that one checkout, a customer session for one signed-in Customer — each sent only to its own routes, none of them a token of your organization (self-serve checkout). And a surface that needs more can supply a client — five methods, the KaitenClientLike interface — that proxies through your backend, where the ksh_ token stays; server-fetched initialData renders the first paint without a request.

Appearance and unstyled

Every styled component renders inside a KaitenStyleRoot, the element that carries the design tokens and scopes every rule of the stylesheet to its subtree. Nothing leaks out of that subtree and nothing leaks in: a .bg-primary of the SDK never reaches your page, your own markup inside a component keeps its own styles, and the stylesheet is delivered outside any cascade layer so that appearance wins over a host stylesheet written in @layer. One root for the whole app is the simple answer; one per route lets pages that only gate pay no DOM at all.

appearance is the single theming entry, set on the style root and merged per component:

KeyControls
baseTheme"light", "dark", or "system". Omitted, the components follow the page — a .dark on an ancestor darkens them.
variablesThe design tokens — primary, background, foreground, card, destructive, success, muted, radius, font — one CSS variable each; the texts and hover fills that are not given are derived.
modesVariables that hold in one color mode only: { light, dark }.
elementsClass names per slot — card, button, badge, usage bar, paywall card, pricing card … — for a utility-class host.
iconsYour icon components per slot — check, lock, alert, loader, infinity …

Six presets (themes.default, midnight, emerald, rose, ocean, slate) are palettes: none decides between light and dark, which stays the host's call. Storybook's Theme playground emits a ready-to-paste appearance containing only what changed. The Cloudflare Turnstile widget of the anonymous checkout follows the color mode too.

Every component also accepts unstyled: bare semantic HTML with the same data-slot attributes and no utility class, for a host that owns its DOM and its CSS entirely. The behavioral rules — what is rendered when, which CTA exists — are the same in both renderings.

The rules the components keep

  • The server is the authority; the component is the hint. Gates and paywalls anticipate the decision the API will make on the next usage report — they do not enforce it. The arithmetic is the one the API uses, with the same meaning: a meter reads 100 % when the customer has consumed what they bought, and keeps climbing inside a soft limit's allowance; the door closes where the server starts refusing. See usage and metering.
  • A component cannot lie about a number. Every amount is the server's: prices come from the catalogue — the public one, or the one you pass in static mode —, what is due from a checkout's dryRun, and lines and totals are shown as the server states them, never summed in the browser. A failed usage read is not 0 %, a limit the catalog does not state is not 0, an unknown usage window is not "lifetime", and a meter is drawn only for a finite numeric quota.
  • A call to action is never a dead end. A paywall renders its button only when onUpgrade is wired; the portal's actions appear for the callbacks you provide and, under a customer session, for what the portal says this customer may do; a License that cannot be bought self-serve links to its selfServeCtaUrl. A card is entered only on the payment provider's own page, never in a component.
  • Fail closed where money is at stake, fail open where it is not. Paywall and Show reveal nothing while loading and block on a data error by default; Gate stays open while loading and outside a provider — a product that has not wired Kaiten yet must not gate itself.
  • Branding follows the License. KaitenBranding renders the "Powered by Kaiten" mark. hideBranding on the provider is a request, honored only where the server says the mark is removable: branding.poweredByRemovable, on the public catalogue and on a customer session's portal — true where your organization's License allows it on Kaiten Cloud, and always on a self-hosted installation. Otherwise the mark stays, and the provider's telemetry, if you configured it, receives a branding.bypass_attempted event. The licensing snapshot of authenticated mode carries no such answer, so the mark stays there.

Vocabulary. The SDK's own identifiers name the License catalog plans — the usePlans() hook, the plan and plans props of the selling and checkout components. These pages say License, as the rest of the documentation does, and quote the identifiers in code font where you will meet them.

📸 TODO media (screenshot): Storybook of @kaitencloud/react-ui — the Appearance → Theme playground story with PricingTable and Paywall rendered side by side in light and dark, the appearance.variables editor open on the left, colorPrimary set to Kaiten violet #8F52EA, and the generated appearance snippet visible at the bottom.

In this section

Where to go next

The credentials and packages behind the components are on the SDK hub, and the public routes they call on self-serve checkout; the server-side chain a gate anticipates is built step by step in gate a feature by License. Exact reference for what the snapshot carries — grants, usage rows, periodic windows: the data model.

On this page