Components
Drop-in billing UI components for React
All components are exported from @nozle-js/react and use --nozle-* CSS custom properties for theming. They work with any design system -- just set the CSS variables on a parent element or :root.
Components that make API calls (CheckoutButton, UpgradeButton, CreditTopUpButton) must be wrapped in a BillingPortalProvider. Components that read subscription data (CurrentPlan, CreditBalance, CreditHistory, InvoiceList) require a BillingProvider ancestor.
Pricing Components
PricingTable
Renders plan cards in a responsive grid with a monthly/annual toggle. Auto-fetches plans from the API when no plans prop is provided, and auto-detects the current plan when customerId is set.
import { PricingTable } from '@nozle-js/react';
// Auto-fetch plans from API, auto-detect current plan
<PricingTable
customerId="cust_123"
highlightPlan="pro"
onSelect={(plan) => handleUpgrade(plan)}
/>
// Or pass plans explicitly
<PricingTable
plans={[
{ code: 'starter', name: 'Starter', amount_cents: 2900, amount_currency: 'USD', interval: 'monthly' },
{ code: 'pro', name: 'Pro', amount_cents: 9900, amount_currency: 'USD', interval: 'monthly', description: 'Most popular' },
]}
features={[
['10K API calls', 'Email support'],
['100K API calls', 'Priority support', 'Analytics'],
]}
currentPlanCode="starter"
highlightPlan="pro"
onSelect={(plan) => console.log('Selected:', plan.code)}
showToggle={true}
enterpriseEmail="sales@example.com"
className="my-pricing"
/>;| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer ID for auto-detecting current plan via API |
currentPlanCode | string | -- | Explicitly set current plan (overrides auto-detect) |
plans | PricingPlan[] | -- | Plans to display (auto-fetched from /api/v1/plans if omitted) |
features | string[][] | -- | Feature lists per plan (index-matched to plans array) |
onSelect | (plan: PricingPlan) => void | -- | Called when a plan's CTA is clicked |
highlightPlan | string | -- | Plan code to highlight as "Most Popular" |
enterpriseEmail | string | -- | Email for enterprise plan "Contact Sales" mailto link |
showToggle | boolean | true | Show monthly/annual toggle (only renders when annual plans exist) |
className | string | -- | CSS class for the outer wrapper |
PricingPlan type
| Field | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Unique plan identifier (matches plan code from API) |
name | string | Yes | Display name |
amount_cents | number | Yes | Price in cents |
amount_currency | string | Yes | Currency code (USD, EUR, GBP, JPY) |
interval | string | Yes | Billing interval (monthly or yearly) |
description | string | No | Subtitle below the plan name |
CSS Variable Theming
PricingTable uses CSS custom properties for full theming control. Component-specific variables fall back to global Nozle variables, which fall back to sensible defaults:
:root {
/* Component-specific (highest priority) */
--nozle-pricing-bg: #ffffff;
--nozle-pricing-card-bg: #ffffff;
--nozle-pricing-highlight: #6366f1;
--nozle-pricing-border: #e5e7eb;
--nozle-pricing-radius: 12px;
/* Global Nozle variables (fallbacks) */
--nozle-background: #ffffff;
--nozle-card: #ffffff;
--nozle-primary: #6366f1;
--nozle-border: #e5e7eb;
--nozle-radius: 12px;
--nozle-foreground: #111827;
--nozle-muted-foreground: #6b7280;
--nozle-muted: #f3f4f6;
--nozle-primary-foreground: #ffffff;
}PlanCard
Individual pricing plan card. Used internally by PricingTable, but can be rendered standalone for custom layouts.
import { PlanCard } from '@nozle-js/react';
<PlanCard
id="pro"
name="Pro"
monthlyPrice={99}
annualPrice={990}
features={['100K API calls', 'Priority support', 'Analytics']}
isAnnual={false}
isCurrent={false}
onSelect={() => handleUpgrade('pro')}
/>;| Prop | Type | Default | Description |
|---|---|---|---|
...Plan | Plan | -- | All fields from the Plan type (see above) |
isAnnual | boolean | -- | Whether to show annual or monthly price |
isCurrent | boolean | -- | Highlights card border and disables CTA |
onSelect | () => void | -- | Click handler for the CTA button |
children | ReactNode | -- | Custom content below the features list |
PlanComparison
Side-by-side feature comparison table. Boolean values render as check/cross icons; strings render as text.
import { PlanComparison } from '@nozle-js/react';
<PlanComparison
plans={plans}
features={[
{ key: 'api_calls', label: 'API Calls', values: { starter: '10K', pro: '100K' } },
{ key: 'analytics', label: 'Analytics', values: { starter: false, pro: true } },
{ key: 'support', label: 'Priority Support', values: { starter: false, pro: true } },
]}
/>;| Prop | Type | Default | Description |
|---|---|---|---|
plans | Plan[] | -- | Plans used as column headers |
features | ComparisonFeature[] | -- | Rows in the comparison table |
ComparisonFeature type
| Field | Type | Description |
|---|---|---|
key | string | Unique row identifier |
label | string | Human-readable feature name |
values | Record<string, string | boolean> | Map of plan.id to value. Booleans render as check/cross icons. |
PlanBadge
Inline badge displaying the plan name with tier-based coloring.
import { PlanBadge } from '@nozle-js/react';
<PlanBadge plan="Pro" tier="pro" />
<PlanBadge plan="Free" tier="free" />
<PlanBadge plan="Enterprise" tier="enterprise" />| Prop | Type | Default | Description |
|---|---|---|---|
plan | string | -- | Text displayed inside the badge |
tier | 'free' | 'starter' | 'pro' | 'enterprise' | 'free' | Controls background and text color |
Checkout and Billing Actions
CheckoutButton
Initiates a checkout session and routes to the appropriate payment processor. Supports Stripe (hosted redirect and embedded Elements) and Razorpay.
Must be used inside a BillingPortalProvider.
import { CheckoutButton } from '@nozle-js/react';
// Stripe hosted checkout (redirect)
<CheckoutButton
planId="pro_monthly"
label="Subscribe"
onError={(err) => console.error(err)}
/>
// Stripe embedded checkout
<CheckoutButton
planId="pro_monthly"
onStripeClientSecret={(secret) => setClientSecret(secret)}
/>
// Razorpay checkout
<CheckoutButton
planId="pro_monthly"
razorpayKeyId="rzp_live_xxx"
onSuccess={(paymentId) => console.log('Paid:', paymentId)}
/>| Prop | Type | Default | Description |
|---|---|---|---|
planId | string | -- | Plan to check out |
label | string | 'Get Started' | Button text |
apiBaseUrl | string | 'https://api.nozle.app' | API base URL |
className | string | -- | CSS class for the button |
style | React.CSSProperties | -- | Inline styles |
onError | (error: Error) => void | -- | Called on checkout failure |
razorpayKeyId | string | -- | Razorpay key_id (required for Razorpay path) |
onStripeClientSecret | (clientSecret: string) => void | -- | Called with the Stripe client secret for embedded Elements |
onSuccess | (paymentId: string) => void | -- | Called with Razorpay payment ID on success |
Checkout
Stripe Elements drop-in checkout form. Mount this after receiving a clientSecret from CheckoutButton or your own backend.
Requires @stripe/react-stripe-js and @stripe/stripe-js as peer dependencies.
import { Checkout } from '@nozle-js/react';
<Checkout
clientSecret="pi_xxx_secret_xxx"
publishableKey="pk_live_xxx"
submitLabel="Pay now"
onSuccess={(paymentIntentId) => router.push('/success')}
onError={(err) => toast.error(err.message)}
onReady={() => setLoading(false)}
/>| Prop | Type | Default | Description |
|---|---|---|---|
clientSecret | string | -- | Stripe PaymentIntent client secret |
publishableKey | string | -- | Stripe publishable key |
stripeAccount | string | -- | Stripe Connect account ID |
submitLabel | string | 'Pay now' | Text on the submit button |
onSuccess | (paymentIntentId: string) => void | -- | Called when payment succeeds without redirect |
onError | (error: Error) => void | -- | Called on payment failure |
onReady | () => void | -- | Called when the PaymentElement becomes interactive |
className | string | -- | CSS class for the outer wrapper |
style | React.CSSProperties | -- | Inline styles for the outer wrapper |
The useCheckout hook is available inside the Checkout tree for programmatic control:
import { useCheckout } from '@nozle-js/react';
function CustomSubmit() {
const { confirmPayment, isProcessing, error } = useCheckout();
return <button onClick={confirmPayment} disabled={isProcessing}>Pay</button>;
}UpgradeButton
Opens an UpgradeModal with a live proration preview. Must be used inside a BillingPortalProvider.
import { UpgradeButton } from '@nozle-js/react';
<UpgradeButton
targetPlanId="pro_monthly"
label="Upgrade to Pro"
onUpgraded={() => router.refresh()}
/>| Prop | Type | Default | Description |
|---|---|---|---|
targetPlanId | string | -- | Plan the customer is upgrading to |
label | string | 'Upgrade' | Button text |
apiBaseUrl | string | 'https://api.nozle.app' | API base URL |
className | string | -- | CSS class for the button |
style | React.CSSProperties | -- | Inline styles |
onUpgraded | () => void | -- | Called after the upgrade is confirmed |
UpgradeModal
Modal dialog showing a proration preview before confirming a plan change. Fetches preview from POST /api/v1/subscriptions/preview and confirms via POST /api/v1/subscriptions/change.
Displays: credit from current plan, new plan charge, net amount due today, and next billing date.
import { UpgradeModal } from '@nozle-js/react';
<UpgradeModal
isOpen={showModal}
targetPlanId="pro_monthly"
customerId="cus_xxx"
apiBaseUrl="https://api.nozle.app"
apiKey="pk_xxx"
onConfirm={() => setShowModal(false)}
onCancel={() => setShowModal(false)}
/>| Prop | Type | Default | Description |
|---|---|---|---|
isOpen | boolean | -- | Controls modal visibility |
targetPlanId | string | -- | Plan the customer is upgrading to |
customerId | string | -- | Customer identifier |
apiBaseUrl | string | 'https://api.nozle.app' | API base URL |
apiKey | string | '' | API key for authorization |
onConfirm | () => void | -- | Called after successful upgrade |
onCancel | () => void | -- | Called when the user dismisses the modal |
ProrationPreview type
| Field | Type | Description |
|---|---|---|
credit | number | Credit from the remaining time on the current plan |
debit | number | Charge for the new plan |
net | number | Amount due today (debit - credit) |
nextBillingDate | string | ISO date string of the next billing cycle |
CreditTopUpButton
Opens a dialog for purchasing credits. Displays preset amounts ($10, $25, $50, $100, $250) with an optional custom amount input.
Must be used inside a BillingPortalProvider.
import { CreditTopUpButton } from '@nozle-js/react';
<CreditTopUpButton
label="Add Credits"
onSuccess={(amount) => toast.success(`Added $${amount} in credits`)}
onError={(err) => toast.error(err.message)}
/>| Prop | Type | Default | Description |
|---|---|---|---|
label | string | 'Add Credits' | Button text |
apiBaseUrl | string | 'https://api.nozle.app' | API base URL |
className | string | -- | CSS class for the button |
style | React.CSSProperties | -- | Inline styles |
onSuccess | (amount: number) => void | -- | Called with the purchased amount |
onError | (error: Error) => void | -- | Called on purchase failure |
CancelSubscriptionButton
Two-step cancellation flow. Step 1 shows a confirmation dialog. Step 2 presents a reason survey with five options: Too expensive, Missing features, Switching to competitor, Not using it enough, and Other (with free-text input). Submits DELETE /api/v1/subscriptions/{id} with the selected reason.
import { CancelSubscriptionButton } from '@nozle-js/react';
<CancelSubscriptionButton
subscriptionId="sub_xxx"
onCancelled={() => router.push('/goodbye')}
/>| Prop | Type | Default | Description |
|---|---|---|---|
subscriptionId | string | -- | Subscription to cancel |
onCancelled | () => void | -- | Called after the cancellation completes |
The exported CANCEL_REASONS constant contains the five reason strings if you need them elsewhere:
import { CANCEL_REASONS } from '@nozle-js/react';
// ["Too expensive", "Missing features", "Switching to competitor", "Not using it enough", "Other"]Billing Portal Components
BillingPortalProvider
Context wrapper that provides { customerId, apiKey } to all nested SDK components. Required by CheckoutButton, UpgradeButton, and CreditTopUpButton.
import { BillingPortalProvider } from '@nozle-js/react';
<BillingPortalProvider customerId="cus_xxx" apiKey="pk_xxx">
<PricingTable plans={plans} />
<CheckoutButton planId="pro" />
</BillingPortalProvider>| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer identifier passed to all child components |
apiKey | string | -- | Publishable API key (pk_ prefix) |
children | ReactNode | -- | Child components |
Access the context directly with the useBillingPortal hook:
import { useBillingPortal } from '@nozle-js/react';
const { customerId, apiKey } = useBillingPortal();BillingPortal
Aggregated portal view that composes CurrentPlan, UsageDashboard, InvoiceList, and CreditBalance into a single vertical layout.
import { BillingPortal } from '@nozle-js/react';
<BillingPortal
customerId="cus_xxx"
usageFeatures={[
{ key: 'api_calls', label: 'API Calls', used: 8200, limit: 10000 },
{ key: 'storage', label: 'Storage (GB)', used: 3, limit: 10 },
]}
onChangePlan={() => router.push('/plans')}
/>| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer identifier |
usageFeatures | UsageDashboardFeature[] | -- | Usage data passed to the embedded UsageDashboard |
usageLoading | boolean | false | Shows skeleton placeholders for the usage section |
onChangePlan | () => void | -- | Called when the "Change Plan" button is clicked |
subscriptionId | string | -- | Subscription ID for the cancel button |
onCancelled | () => void | -- | Called after subscription cancellation |
CurrentPlan
Displays the customer's active subscription: plan name (with PlanBadge), billing interval, status, and next billing date. Fetches from GET /api/v1/subscriptions/current.
import { CurrentPlan } from '@nozle-js/react';
<CurrentPlan
customerId="cus_xxx"
onChangePlan={() => router.push('/plans')}
/>| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer identifier |
onChangePlan | () => void | -- | Callback for the "Change Plan" button. If omitted, navigates to changePlanHref. |
changePlanHref | string | '/plans' | Fallback URL when onChangePlan is not provided |
CurrentPlanData type
| Field | Type | Description |
|---|---|---|
id | string | Subscription ID |
planName | string | Display name of the plan |
interval | string | Billing interval (e.g., "monthly", "annual") |
status | string | Subscription status (e.g., "active", "canceled") |
nextBillingDate | string | ISO date of next charge |
tier | 'free' | 'starter' | 'pro' | 'enterprise' | Controls badge styling |
CreditBalance
Displays the customer's current credit balance. Uses the useCredits hook internally.
import { CreditBalance } from '@nozle-js/react';
<CreditBalance customerId="cus_xxx" currency="USD" />| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer identifier |
currency | string | 'USD' | Currency code for formatting (e.g., 'EUR', 'GBP') |
CreditHistory
Renders a table of credit transactions sorted newest-first. Shows up to 20 rows with a "View all" link when there are more.
import { CreditHistory } from '@nozle-js/react';
<CreditHistory
customerId="cus_xxx"
viewAllHref="/billing/credits"
/>| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer identifier |
viewAllHref | string | '/billing/credits' | Link target for "View all" when there are more than 20 transactions |
CreditTransaction type
| Field | Type | Description |
|---|---|---|
id | string | Transaction ID |
amount | number | Credit amount |
type | string | One of 'grant', 'deduct', 'purchase', 'application' |
description | string | Human-readable description |
createdAt | string | ISO timestamp |
InvoiceList
Displays customer invoices in a table with status badges and PDF download links. Fetches from GET /api/v1/invoices.
import { InvoiceList } from '@nozle-js/react';
<InvoiceList customerId="cus_xxx" />| Prop | Type | Default | Description |
|---|---|---|---|
customerId | string | -- | Customer identifier |
Invoice type
| Field | Type | Description |
|---|---|---|
id | string | Invoice ID |
number | string | Invoice number (e.g., INV-001) |
date | string | ISO date string |
amount | number | Amount in cents |
currency | string | Currency code |
status | 'draft' | 'open' | 'paid' | 'void' | 'uncollectible' | Invoice status |
pdf_url | string | URL to download the invoice PDF |
PaymentMethodDisplay
Shows the customer's saved payment method (card brand, last 4 digits, expiry) with an "Update Payment Method" button.
import { PaymentMethodDisplay } from '@nozle-js/react';
<PaymentMethodDisplay
paymentMethod={{
brand: 'visa',
last4: '4242',
expMonth: 12,
expYear: 2027,
}}
onUpdatePaymentMethod={() => setShowCheckout(true)}
/>| Prop | Type | Default | Description |
|---|---|---|---|
paymentMethod | PaymentMethod | -- | Card details to display. Shows "No payment method on file." when undefined. |
onUpdatePaymentMethod | () => void | -- | Click handler for the update button. If omitted, navigates to updateHref. |
updateHref | string | '/billing/update-payment' | Fallback URL when onUpdatePaymentMethod is not provided |
PaymentMethod type
| Field | Type | Description |
|---|---|---|
last4 | string | Last 4 digits of the card |
brand | string | Card brand (visa, mastercard, amex, discover, jcb, diners, unionpay) |
expMonth | number | Expiration month (1-12) |
expYear | number | Expiration year (4-digit) |
Usage Components
UsageMeter
Progress bar with automatic color thresholds based on usage percentage.
| Percentage | Color zone |
|---|---|
| < 80% | Primary (normal) |
| 80 -- 94% | Warning (amber) |
| >= 95% | Destructive (red) |
import { UsageMeter } from '@nozle-js/react';
<UsageMeter used={8200} limit={10000} label="API Calls" />
<UsageMeter used={9800} limit={10000} label="API Calls" showText={false} />| Prop | Type | Default | Description |
|---|---|---|---|
used | number | -- | Current usage count |
limit | number | -- | Maximum allowed usage |
label | string | -- | Label shown above the progress bar |
showText | boolean | true | Whether to display the "used / limit" text |
The getUsageMeterColor helper is also exported for use in custom components:
import { getUsageMeterColor } from '@nozle-js/react';
const color = getUsageMeterColor(92); // returns the warning CSS variableUsageDashboard
Renders multiple UsageMeter bars for all tracked features. Shows skeleton placeholders when loading.
import { UsageDashboard } from '@nozle-js/react';
<UsageDashboard
features={[
{ key: 'api_calls', label: 'API Calls', used: 8200, limit: 10000 },
{ key: 'storage', label: 'Storage (GB)', used: 3, limit: 10 },
{ key: 'seats', label: 'Team Seats', used: 4, limit: 5 },
]}
/>| Prop | Type | Default | Description |
|---|---|---|---|
features | UsageDashboardFeature[] | [] | Array of usage features to display |
loading | boolean | false | Shows skeleton placeholders when true |
UsageDashboardFeature type
| Field | Type | Description |
|---|---|---|
key | string | Unique feature identifier |
label | string | Human-readable feature name |
used | number | Current usage count |
limit | number | Maximum allowed usage |
UsageAlert
Dismissible warning banner shown when any feature's usage percentage meets or exceeds the threshold. Includes an upgrade link.
import { UsageAlert } from '@nozle-js/react';
<UsageAlert
features={[
{ key: 'api_calls', label: 'API Calls', percentage: 92 },
{ key: 'storage', label: 'Storage', percentage: 45 },
]}
threshold={80}
upgradeHref="/plans"
/>Only features at or above the threshold are shown. In the example above, only the "API Calls" alert would render (92% >= 80%). The "Storage" alert would not appear (45% < 80%).
| Prop | Type | Default | Description |
|---|---|---|---|
features | UsageAlertFeature[] | -- | Array of features with their current usage percentage |
threshold | number | 80 | Minimum percentage to trigger a warning |
upgradeHref | string | '/plans' | URL for the "Upgrade" link in each alert |
UsageAlertFeature type
| Field | Type | Description |
|---|---|---|
key | string | Unique feature identifier |
label | string | Human-readable feature name |
percentage | number | Current usage percentage (0-100) |