Design is the single most important factor in payment conversion. A checkout that feels broken, slow, or unfamiliar will lose customers — no matter how good the backend is. Every border radius, every color, every animation exists to build trust in the 3–5 seconds a user decides whether to complete their payment.
That’s why we built a unified design system that powers every billing.io surface from a single package. Change one file, publish, and the update propagates to hosted checkouts, invoices, embedded overlays — everything.
All billing.io payment UI is owned by a single NPM package:@billing-io/designs. The hosted checkout, the hosted invoice, and the embedded overlays all import their components from here. There is no UI duplication.
NPM Package
@billing-io/designs
Single source of truth
Hosted
Checkout & Invoice
Direct React import
pay.billing.io/checkout/...
Embedded
billing.js Overlays
iframe → hosted card
js.billing.io/v1/billing.js
Design Site
This Preview App
Direct React import
localhost:3003
@billing-io/designsis published to NPM and lives on GitHub atbilling-io/designs. It exports three modules:
checkout/
CheckoutCard component, all SVG icons, payment state types, wallet deep-link builders.
overlay/
Overlay constants (dimensions, backdrop, border radius) and the MSG_PREFIX for the iframe bridge.
brand
The canonical “b.” logo SVG, badge builder, and brand constants used in every footer.
// Hosted checkout (Next.js app on pay.billing.io)
import { CheckoutCard } from "@billing-io/designs/checkout";
import type { PaymentDisplayState } from "@billing-io/designs/checkout";
// Rendering the checkout — all visual design is owned by the package.
// The consumer only passes data props.
<CheckoutCard
status="paying"
amount="$20.00"
token="USDT"
chain="Tron"
address="TN7hMd7PKEaZLMZe8eQhWpGjEpJJFbGSxE"
merchantName="your-app.com"
/>There are two fundamentally different ways a merchant’s customer sees a billing.io checkout. Both render the exact same CheckoutCard, but the delivery mechanism is different.
The merchant creates a checkout session via our API and redirects the customer topay.billing.io/checkout/co_.... This is a Next.js app that directly imports CheckoutCard from the package. Same for invoices.
Because it’s a direct React import, the hosted checkout gets type safety, tree-shaking, and instant hot reloads during development. When we publish a new version of @billing-io/designs, we bump the dependency in the hosted app, redeploy, and every live checkout page is updated.
The merchant drops a <script> tag on their site that loads billing.js. When they call billing.openCheckout(), the script creates an overlay (centered modal, side panel, bottom sheet, or fullscreen) with an iframe inside that loads the hosted checkout page.
The iframe and the parent page communicate via postMessage. The checkout sends status updates (billing:status,billing:resize) so the overlay can react to payment state changes and auto-size.
Why an iframe?
// billing.js (simplified) — what happens when a merchant calls openCheckout()
billing.openCheckout({
checkoutId: "co_abc123",
variation: "centered", // or "panel", "bottom", "fullscreen"
});
// 1. billing.js creates a backdrop + overlay container on the merchant's page
// 2. It injects an <iframe> pointing to the hosted checkout:
// src="https://pay.billing.io/checkout/co_abc123?embed=1&fill=1"
// 3. The checkout page renders CheckoutCard with isEmbed=true
// 4. The iframe sends postMessage events back to billing.js:
// "billing:loaded" → overlay fades in
// "billing:status" → { status: "detected" } → overlay reacts
// "billing:resize" → { height: 640 } → bottom sheet auto-sizes
// 5. On success, billing.js calls the merchant's onSuccess callbackBecause every surface imports from the same package, we can test design changes across the entire product with a single publish cycle:
billing-designs/src/checkout/checkout-card.tsxnpm publishThis is how we just shipped the minimal white wallet buttons (MM5 + TW5). One file change in the design package, one publish to @billing-io/designs@1.6.2, and the new style is live across every integration mode.
The package is intentionally small and focused. No external UI dependencies — all icons are inline SVGs, all styling is Tailwind CSS.
src/
├── checkout/
│ ├── checkout-card.tsx ← The checkout UI (single source of truth)
│ ├── icons.tsx ← All SVG icons (Lock, Shield, MetaMask, TrustWallet, etc.)
│ ├── types.ts ← CheckoutCardProps, PaymentDisplayState
│ └── index.ts ← Public exports
├── overlay/
│ ├── constants.ts ← Overlay dimensions (centered, panel, bottom sheet, fullscreen)
│ └── index.ts ← MSG_PREFIX for iframe postMessage bridge
├── brand.ts ← "b." logo SVG paths, badge builder
├── index.ts ← Root exports
└── billing-js/
└── billing.js ← The drop-in script for embedded overlaysRight now the checkout modal uses a fixed design. In the next version, we’re opening it up so merchants can fully customize the checkout experience to match their brand:
Custom Logo
Upload a logo that appears in the checkout header instead of the default merchant name text. SVG, PNG, or ICO.
Custom Domain
Serve the hosted checkout from the merchant’s own domain, e.g. pay.your-app.com instead of pay.billing.io.
Brand Colors
Set a primary accent color that tints buttons, badges, and the copy-address CTA. The design system will generate accessible contrast ratios automatically.
Custom Copy
Override default strings like “via billing.io secure checkout” with merchant-specific text. Useful for white-label deployments.
Technically, this will work by extending the CheckoutCardProps interface with an optional branding object. The merchant configures their branding in the dashboard, it gets stored on the checkout session, and the hosted app passes it to CheckoutCard as props. Because embedded checkouts use an iframe to the hosted page, merchants using billing.js get branding for free — no code changes on their side.
// What the branding prop will look like
<CheckoutCard
status="paying"
amount="$20.00"
token="USDT"
chain="Tron"
address="TN7hMd7PKEaZLMZe8eQhWpGjEpJJFbGSxE"
merchantName="Acme Inc."
branding={{
logo: "https://cdn.your-app.com/logo.svg",
accentColor: "#6366F1", // indigo-500
headerText: "Pay securely with Acme",
showPoweredBy: true, // "Powered by billing.io" in footer
}}
/>
// The same props get passed through to embedded checkouts automatically.
// Merchant configures branding once in the dashboard → stored on the
// checkout session → rendered by CheckoutCard → works in hosted AND
// embedded modes with zero extra work.