BlocksUpdated September 15, 2026

Billing overview

A presentation-only billing summary with consumer-owned plan, billing status, renewal, payment details and actions.

Present billing context without owning billing logicLink to section

BillingOverviewPanel gives an application a compact billing summary while keeping billing truth and every action in the consuming product. Use it to show plan, billing cadence, status, renewal and payment information that your own billing layer has already resolved.

Presentation only

The Block does not query Stripe, calculate billing periods, change a plan, open a portal, renew a subscription or authorize an action. Supply truthful values and connect actions through your own validated billing boundary.

Canonical identityLink to section

  • Category: Commerce, slug commerce.
  • Block: billing-overview, export BillingOverviewPanel.
  • Canonical source: apps/marketing/content/blocks/commerce/billing-overview/.
  • Complete entry point: index.tsx.

Copy sourceLink to section

Configure the public UI package and tokens, then create src/components/blocks/billing-overview/index.tsx in your application.

Open the complete source below and use the code block copy button when available. If clipboard access is unavailable, select the code manually.

View and copy the complete source
src/components/blocks/billing-overview/index.tsx
import * as React from "react";

export type BillingOverviewPanelProps = Readonly<{
  title: React.ReactNode;
  description?: React.ReactNode;
  planLabel: React.ReactNode;
  planValue: React.ReactNode;
  billingLabel?: React.ReactNode;
  billingValue?: React.ReactNode;
  statusLabel?: React.ReactNode;
  statusValue?: React.ReactNode;
  renewalLabel?: React.ReactNode;
  renewalValue?: React.ReactNode;
  paymentLabel?: React.ReactNode;
  paymentValue?: React.ReactNode;
  primaryAction?: React.ReactNode;
  secondaryAction?: React.ReactNode;
  className?: string;
}>;

/**
 * Presentation-only billing summary. Billing truth and every action remain
 * entirely owned by the consuming application.
 */
export function BillingOverviewPanel({
  title,
  description,
  planLabel,
  planValue,
  billingLabel,
  billingValue,
  statusLabel,
  statusValue,
  renewalLabel,
  renewalValue,
  paymentLabel,
  paymentValue,
  primaryAction,
  secondaryAction,
  className,
}: BillingOverviewPanelProps) {
  const id = React.useId();
  const titleId = `${id}-title`;
  const descriptionId = `${id}-description`;
  const rootClassName = [
    "min-w-0 space-y-6 rounded-xl border border-border bg-card p-5 text-card-foreground sm:p-6",
    className,
  ]
    .filter(Boolean)
    .join(" ");

  const details = [
    { label: planLabel, value: planValue },
    billingLabel && billingValue
      ? { label: billingLabel, value: billingValue }
      : null,
    statusLabel && statusValue
      ? { label: statusLabel, value: statusValue }
      : null,
    renewalLabel && renewalValue
      ? { label: renewalLabel, value: renewalValue }
      : null,
    paymentLabel && paymentValue
      ? { label: paymentLabel, value: paymentValue }
      : null,
  ].filter(
    (
      detail,
    ): detail is Readonly<{
      label: React.ReactNode;
      value: React.ReactNode;
    }> => detail !== null,
  );

  return (
    <section
      aria-describedby={description ? descriptionId : undefined}
      aria-labelledby={titleId}
      className={rootClassName}
      data-slot="billing-overview-panel"
    >
      <header className="min-w-0 space-y-2">
        <h2 className="break-words text-xl font-semibold" id={titleId}>
          {title}
        </h2>
        {description ? (
          <div
            className="break-words text-sm text-muted-foreground"
            id={descriptionId}
          >
            {description}
          </div>
        ) : null}
      </header>

      <dl className="grid min-w-0 gap-4 sm:grid-cols-2">
        {details.map((detail, index) => (
          <div
            className="min-w-0 rounded-lg border border-border bg-background p-4"
            key={index}
          >
            <dt className="text-sm font-medium text-muted-foreground">
              {detail.label}
            </dt>
            <dd className="mt-1 break-words text-sm font-medium">
              {detail.value}
            </dd>
          </div>
        ))}
      </dl>

      {primaryAction || secondaryAction ? (
        <div className="flex min-w-0 flex-col gap-3 sm:flex-row sm:flex-wrap">
          {primaryAction}
          {secondaryAction}
        </div>
      ) : null}
    </section>
  );
}

The documentation includes the canonical file at build time. Your application owns the copied source and does not receive automatic updates. Review future changes deliberately and validate it in your application before adoption. Return to the Blocks catalog to compare another pattern.

Install by copying sourceLink to section

  1. Copy the complete billing-overview directory into your application-owned Blocks path.
  2. Configure the public PyColors UI package and tokens through the UI installation guide.
  3. Supply billing facts and actions from your own application state.
  4. Run lint, type-check, tests and build, then review narrow widths, long values, both themes and keyboard behavior.
src/components/billing-summary.tsx
"use client";

import { BillingOverviewPanel } from "./blocks/billing-overview";

export function BillingSummary() {
  return (
    <BillingOverviewPanel
      title="Billing"
      description="Example values supplied by your application."
      planLabel="Plan"
      planValue="Growth"
      billingLabel="Billing"
      billingValue="Monthly"
      statusLabel="Status"
      statusValue="Active"
      renewalLabel="Renews"
      renewalValue="October 15"
      paymentLabel="Payment method"
      paymentValue="Visa ending in 4242"
      primaryAction={
        <button type="button" onClick={() => undefined}>
          Manage billing
        </button>
      }
    />
  );
}

The example action is intentionally local. Replace it with your own route or server-backed billing flow only after validating permissions and current billing state.

Consumer-owned contractLink to section

title, planLabel and planValue are required. Description, billing, status, renewal and payment pairs are optional. Provide both the label and value for an optional detail when you want it rendered.

primaryAction and secondaryAction are React nodes supplied unchanged by the consumer. The Block does not infer whether a user may change plans, cancel, update payment details or open a billing portal. Authorization remains outside the presentation layer.

Responsive and accessibility notesLink to section

The summary uses a labelled section, semantic definition-list markup and a responsive two-column details grid. Long values wrap instead of forcing page width. Preserve meaningful action names, visible focus and native disabled semantics in controls you supply.

Ownership and boundariesLink to section

Your application owns billing data, pricing truth, subscription state, Stripe or other provider integration, permissions, mutations, redirects and error handling. The Block does not include checkout, Customer Portal, webhooks, persistence, entitlements, authentication, analytics, Registry or CLI behavior.

Explore the Blocks catalog, compare Pricing plans, or compare Starters for a complete application foundation.