Skip to navigation

Drop-in Components

Drop-in components are React components from @schematichq/schematic-components/elements. Each one reads one or more resources through the hooks in @schematichq/schematic-react, reduces them to a display model with a pure derivation, and renders with CSS-variable styling. You configure them with props, not a builder.

ComponentWhat it shows
CheckoutThe purchase flow
Plan ManagerCurrent plan, add-ons, usage-based features, credits, and the notice that matters now
Pricing TablePlans and add-ons, public or company
Included FeaturesEntitlements with limits and usage
Metered FeaturesUsage against each metered feature’s limit, its cost, and usage by user
Credit UsageCredit balances, the grants behind them, and usage by user
Upcoming BillThe next bill, balance applied, discounts
Payment MethodsThe method on file; switch the default, remove a method, or add one through Stripe
InvoicesInvoice history with links to hosted invoices
Unsubscribe ButtonCancel at period end, with a confirmation

Using an agent? Paste this into Claude Code, Cursor, or Codex and it does the setup on this page:

Add Schematic billing to my app: docs.schematichq.com/components/skill.md

Setup

import { SchematicProvider } from "@schematichq/schematic-react";
import {
CheckoutLauncherProvider, CreditUsage, IncludedFeatures, Invoices, MeteredFeatures,
PaymentMethods, PlanManager, SchematicStyles, UnsubscribeButton, UpcomingBill,
useCheckoutLauncher,
} from "@schematichq/schematic-components/elements";
export function Billing() {
return (
<SchematicProvider
publishableKey="pk_…"
session={{ company: company.id, token: fetchAccessToken }}
>
<SchematicStyles />
<CheckoutLauncherProvider>
<Portal />
</CheckoutLauncherProvider>
</SchematicProvider>
);
}
function Portal() {
const { open } = useCheckoutLauncher();
return (
<>
<PlanManager onChangePlan={() => open()} />
<IncludedFeatures />
<MeteredFeatures onAddMore={() => open({ steps: { initial: "usage" } })} />
<CreditUsage onBuyMore={() => open({ steps: { initial: "credits" } })} />
<UpcomingBill />
<PaymentMethods />
<Invoices />
<UnsubscribeButton onManagePlan={() => open()} />
</>
);
}

Drop any card you do not need; each stands alone. CheckoutLauncherProvider renders one <Checkout /> for the page and useCheckoutLauncher().open() opens it. Plan Manager, Metered Features, Credit Usage, and Unsubscribe Button show Change plan, Add More, Buy More, and Manage plan only when you pass a handler or URL, so Portal hands each of them open.

Copy and formatting stay yours. locale on the provider formats every component through Intl, and each string resolves through strings on the component, then strings on the provider, then your translate, then the component’s English. Nothing has to be configured, and renaming one string is one prop. See Localization.

The ./elements entry is its own bundle and keeps React and both Schematic SDKs external, so the provider your app renders is the one the components read. @schematichq/schematic-react is a peer of this entry alone.

Components that take a payment, such as Payment Methods and Checkout, render Stripe’s Payment Element from @stripe/stripe-js and @stripe/react-stripe-js. Both are peer dependencies of the components, so your package manager installs them alongside. They load only when a payment form opens, so a page that never opens one never downloads Stripe.

The session

session says whose billing is being read and how. It has three states, and they mean different things:

session={{ company: company.id, token: fetchAccessToken }} // signed in
session={null} // signed out
session={undefined} // still finding out

The identity is the (company, user) pair, because that is what a token is minted for. company is your id for the company. user is optional: leave it out where your tokens are company-wide, and state it where you mint per person.

null signs out: every card empties until a session returns. undefined says you do not know yet, which is what an auth resolving asynchronously renders first. It changes nothing: before any session the cards hold their loading state rather than reporting an error, and after one they keep showing it until you pass a new session or null. Pricing Table shows the public catalog in both. Auth libraries can blur the two, so read their loaded and signed-in flags before deciding which you mean. Session and access tokens covers token providers, refresh, and server rendering.

Before it can load

The company endpoints are enabled per account. An account without them gets a 404 from every /company/* read, and because a 404 on the invoice history is not an answer (an empty history is a 200 with no rows), the component reports it as an error rather than showing an empty card. If a correctly configured page shows a component’s unavailable message, such as “Invoices are not available for this account.”, ask Schematic to enable company context for the account.

A failure with nothing to show replaces a card’s content with a short message and a “Try again” button. A failure with rows already on screen, such as a refetch or a later page, is reported under the rows instead. The message is the component’s own copy rather than the error’s text. In development the component also logs the underlying error to the console, so a missing provider or a refused request is diagnosable from the page.

Styling

<SchematicStyles /> injects one stylesheet driven by --schematic-* custom properties, follows your color-scheme for light and dark, and never wins a cascade against your own tokens. Or skip it and style the class names, which are API. Styling and theming has the token list and the class contract.

Mixing with your own UI

Every drop-in is built on the public hooks and derivations, so you can replace any one of them with your own component and keep the rest. A custom Plan Manager that opens the drop-in Checkout through useCheckoutLauncher() is a common shape. Build your own UI starts there.