Payment method
A presentation-only payment-method panel with consumer-owned method, expiry, contact, status and actions.
Present payment details without owning payment behaviorLink to section
PaymentMethodPanel gives an application a bounded surface for displaying the
payment method it already knows about. Use it for method, expiry, billing contact,
status and consumer-owned actions while your billing provider and server remain
the source of truth.
No payment mutation inside the Block
The Block never stores card data, opens Stripe, attaches or detaches a payment method, validates billing details or decides whether an update is allowed. Those operations remain application-owned.
Canonical identityLink to section
- Category: Commerce, slug
commerce. - Block:
payment-method, exportPaymentMethodPanel. - Canonical source:
apps/marketing/content/blocks/commerce/payment-method/. - Complete entry point:
index.tsx.
Copy sourceLink to section
Configure the public UI package and tokens, then create
src/components/blocks/payment-method/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
import * as React from "react";
export type PaymentMethodPanelProps = Readonly<{
title: React.ReactNode;
description?: React.ReactNode;
methodLabel: React.ReactNode;
methodValue: React.ReactNode;
expiryLabel?: React.ReactNode;
expiryValue?: React.ReactNode;
contactLabel?: React.ReactNode;
contactValue?: React.ReactNode;
status?: React.ReactNode;
primaryAction?: React.ReactNode;
secondaryAction?: React.ReactNode;
className?: string;
}>;
/**
* Presentation-only payment-method surface. Billing truth and every action
* remain entirely owned by the consuming application.
*/
export function PaymentMethodPanel({
title,
description,
methodLabel,
methodValue,
expiryLabel,
expiryValue,
contactLabel,
contactValue,
status,
primaryAction,
secondaryAction,
className,
}: PaymentMethodPanelProps) {
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: methodLabel, value: methodValue },
expiryLabel && expiryValue
? { label: expiryLabel, value: expiryValue }
: null,
contactLabel && contactValue
? { label: contactLabel, value: contactValue }
: 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="payment-method-panel"
>
<header className="min-w-0 space-y-2">
<div className="flex min-w-0 flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div 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}
</div>
{status ? <div className="shrink-0">{status}</div> : null}
</div>
</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
- Copy the complete
payment-methoddirectory into your application-owned Blocks path. - Configure the public PyColors UI package and tokens through the UI installation guide.
- Supply payment facts, status and actions from your validated billing layer.
- Run lint, type-check, tests and build, then review real labels, long values, narrow widths, both themes and keyboard behavior.
"use client";
import { PaymentMethodPanel } from "./blocks/payment-method";
export function PaymentMethodSummary() {
return (
<PaymentMethodPanel
title="Payment method"
description="Example values supplied by your application."
methodLabel="Method"
methodValue="Visa ending in 4242"
expiryLabel="Expires"
expiryValue="10 / 28"
contactLabel="Billing contact"
contactValue="billing@example.com"
status={<span>Default</span>}
primaryAction={
<button type="button" onClick={() => undefined}>
Update payment method
</button>
}
/>
);
}The example action performs no payment operation. Replace it with your own provider-backed flow only after validating permissions and current account state.
Consumer-owned contractLink to section
title, methodLabel and methodValue are required. Description, expiry and
contact pairs are optional. status can present consumer-owned context such as a
default-method badge, but it does not carry billing semantics by itself.
primaryAction and secondaryAction are rendered exactly as supplied. The Block
does not clone controls, intercept events or infer whether a user may edit or
remove a method.
Responsive and accessibility notesLink to section
The panel uses a labelled section, semantic definition-list markup and a
responsive details grid. Keep card or bank identifiers appropriately redacted,
provide meaningful control names and preserve visible focus and native disabled
semantics in the actions you supply.
Ownership and boundariesLink to section
Your application owns provider tokens, PCI-sensitive handling, billing contacts, payment-method lifecycle, permissions, mutations, redirects and errors. The Block does not include Stripe Elements, SetupIntents, Customer Portal, checkout, webhooks, persistence, authentication, analytics, Registry or CLI behavior.
Explore the Blocks catalog, compare Billing overview, or compare Starters for a complete application foundation.