Skip to navigation

Build Your Own UI

Everything a drop-in component shows is available to your own code, one layer down. Read the data with a hook, reduce it with a derivation, and render it in your own JSX. The drop-ins are built on the same surface.

How it layers

LayerPackageWhat it gives you
Client@schematichq/schematic-jsSchematicBillingClient and SchematicSession: the session, the requests, the generated types, and fetchBillingData for server rendering. Plain TypeScript, so Vue and Angular use it too.
Hooks@schematichq/schematic-reactSchematicProvider with session, and one hook per resource returning { data, error, isPending, refetch }. A resource you can change carries its writes on the same handle, such as setDefault and remove on usePaymentMethods, and an action with nothing to read, such as useSetupIntent or useUnsubscribe, has a hook of its own.
Derivations@schematichq/schematic-components/elementsPure functions from server facts to a display model, plus Intl formatting helpers.
Your JSXyour appYour own components, starting from the recipes.

Shipped code and reference code

The derivations are shipped code with tests, not copy-paste examples. The display decision trees behind them (which of seven ways an entitlement reads, how tiered pricing prices a quantity, how a credit’s grants add up) are intricate and correctness-sensitive, and a copy would drift from the drop-ins the first time a rule changed. Import them.

The JSX is reference code you own. Each recipe is a complete component; copy the one closest to what you need and restyle it. The Components page in the Schematic dashboard has reference code for each drop-in in shadcn/ui, Tailwind, and plain CSS, plus a prompt for your coding agent.

The recipe pattern

import { useInvoices } from "@schematichq/schematic-react";
import { deriveInvoiceList } from "@schematichq/schematic-components/elements";
export function InvoiceHistory() {
const { data, error, isPending, refetch, loadMore } = useInvoices(); // 1. read
if (data === undefined) {
return error ? <button onClick={refetch}>Retry</button> : <p>Loading…</p>;
}
const list = deriveInvoiceList(data, { locale: "en-US" }); // 2. derive
return ( // 3. render
<ul>
{list.rows.map((row) => (
<li key={row.id}>
{row.dateText} {row.isCredit ? `(${row.amountText})` : row.amountText}
</li>
))}
{list.hasMore && <button disabled={isPending} onClick={loadMore}>Load more</button>}
</ul>
);
}

Every derivation returns structured parts with the raw value beside the formatted text, not assembled sentences, so your wording and your translation stay in your code.

Mixing with drop-ins

The hooks and the drop-ins share one provider and one cache, so a page can use both. A custom Plan Manager can open the drop-in <Checkout/> through useCheckoutLauncher().open(), the same dialog the drop-ins use. A page that shows Included Features and Metered Features beside your own usage chart still makes one request for feature usage.