Invoice history
A presentation-only invoice table with consumer-owned dates, amounts, statuses, actions and empty state.
Present invoice history without owning invoice retrievalLink to section
InvoiceHistoryPanel presents invoice records supplied by your application in a
readable table with an explicit empty state. Your billing backend remains
responsible for fetching records, formatting monetary truth and authorizing any
download or detail action.
Consumer-owned invoice data
The Block does not fetch invoices, generate documents, format currency, resolve payment status or authorize downloads. Pass already-resolved display values and connect actions through your own validated billing boundary.
Canonical identityLink to section
- Category: Commerce, slug
commerce. - Block:
invoice-history, exportInvoiceHistoryPanel. - Canonical source:
apps/marketing/content/blocks/commerce/invoice-history/. - Complete entry point:
index.tsx.
Copy sourceLink to section
Configure the public UI package and tokens, then create
src/components/blocks/invoice-history/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 InvoiceHistoryItem = Readonly<{
id: string;
date: React.ReactNode;
amount: React.ReactNode;
status?: React.ReactNode;
action?: React.ReactNode;
}>;
export type InvoiceHistoryPanelProps = Readonly<{
title: React.ReactNode;
description?: React.ReactNode;
invoices: readonly InvoiceHistoryItem[];
dateLabel?: React.ReactNode;
amountLabel?: React.ReactNode;
statusLabel?: React.ReactNode;
actionLabel?: React.ReactNode;
emptyState?: React.ReactNode;
className?: string;
}>;
/**
* Presentation-only invoice history surface. Invoice data, retrieval and every
* action remain entirely owned by the consuming application.
*/
export function InvoiceHistoryPanel({
title,
description,
invoices,
dateLabel = "Date",
amountLabel = "Amount",
statusLabel = "Status",
actionLabel = "Action",
emptyState = "No invoices yet.",
className,
}: InvoiceHistoryPanelProps) {
const id = React.useId();
const titleId = `${id}-title`;
const descriptionId = `${id}-description`;
const rootClassName = [
"min-w-0 space-y-5 rounded-xl border border-border bg-card p-5 text-card-foreground sm:p-6",
className,
]
.filter(Boolean)
.join(" ");
return (
<section
aria-describedby={description ? descriptionId : undefined}
aria-labelledby={titleId}
className={rootClassName}
data-slot="invoice-history-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>
{invoices.length === 0 ? (
<div
className="rounded-lg border border-dashed border-border bg-background p-5 text-sm text-muted-foreground"
data-slot="invoice-history-empty"
>
{emptyState}
</div>
) : (
<div className="min-w-0 overflow-x-auto rounded-lg border border-border">
<table className="w-full min-w-[36rem] border-collapse text-left text-sm">
<thead className="bg-muted/50 text-muted-foreground">
<tr>
<th className="px-4 py-3 font-medium" scope="col">
{dateLabel}
</th>
<th className="px-4 py-3 font-medium" scope="col">
{amountLabel}
</th>
<th className="px-4 py-3 font-medium" scope="col">
{statusLabel}
</th>
<th className="px-4 py-3 text-right font-medium" scope="col">
{actionLabel}
</th>
</tr>
</thead>
<tbody className="divide-y divide-border">
{invoices.map((invoice) => (
<tr className="bg-background align-middle" key={invoice.id}>
<td className="max-w-48 break-words px-4 py-3">
{invoice.date}
</td>
<td className="max-w-48 break-words px-4 py-3 font-medium">
{invoice.amount}
</td>
<td className="max-w-48 break-words px-4 py-3">
{invoice.status ?? "—"}
</td>
<td className="px-4 py-3 text-right">
{invoice.action ? (
<div className="inline-flex min-w-0 justify-end">
{invoice.action}
</div>
) : (
<span aria-hidden="true">—</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</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
invoice-historydirectory into your application-owned Blocks path. - Configure the public PyColors UI package and tokens through the UI installation guide.
- Supply invoice rows from your own billing data and connect any row action.
- Run lint, type-check, tests and build, then review overflow, long values, empty data, both themes and keyboard behavior.
"use client";
import { InvoiceHistoryPanel } from "./blocks/invoice-history";
const invoices = [
{
id: "invoice-example-1",
date: "September 1, 2026",
amount: "$49.00",
status: "Paid",
action: <a href="/billing/invoices/example">View invoice</a>,
},
] as const;
export function InvoiceHistory() {
return (
<InvoiceHistoryPanel
title="Invoice history"
description="Example records supplied by your application."
invoices={invoices}
emptyState="No invoices are available."
/>
);
}The example destination is application-owned. Connect invoice files or provider records only through a route that rechecks authorization on the server.
Consumer-owned contractLink to section
title and invoices are required. Each invoice needs a stable id, display
date and display amount; status and action are optional. Column labels and
the empty state can be replaced with product-specific copy.
The Block renders the supplied values without calculating totals, formatting currency, translating provider states or deciding whether an invoice action is available. Keep row IDs stable and make action labels specific to their result.
Responsive and accessibility notesLink to section
The panel uses a labelled section and a semantic table when records exist. The
table keeps a readable minimum width inside a horizontal overflow container so
content is not crushed on narrow screens. Test the containing layout and ensure
keyboard users can reach every interactive row action without losing context.
Ownership and boundariesLink to section
Your application owns invoice retrieval, currency formatting, tax and billing truth, document storage, signed URLs, authorization, provider integration, mutations and error handling. The Block does not include invoice generation, download services, Stripe APIs, checkout, webhooks, persistence, authentication, analytics, Registry or CLI behavior.
Explore the Blocks catalog, compare Billing overview, or compare Starters for a complete application foundation.