Skip to Content
BlocksInvoiceDetailBlock

InvoiceDetailBlock

Inspect invoice lines, taxes, adjustments, the amount due, its status and the available payment actions.

Plan idea 83, category Finance & billing. Status experimental, verification level unmeasured — see verification before adopting it.

Import

import { InvoiceDetailBlock } from "@raydenui/ui/blocks"; import type { InvoiceLine, InvoiceAdjustment, InvoiceParty } from "@raydenui/ui/blocks";

Usage

Amounts are minor-unit integers in the invoice’s single currency. A negative amount is a credit.

const lines: InvoiceLine[] = [ { id: "design", description: "Interface design", detail: "1–15 September 2026", quantity: 37.5, quantityUnit: "hours", unitPrice: 9500, amount: 356250, }, { id: "credit", description: "Goodwill credit", amount: -15000 }, ]; <InvoiceDetailBlock invoiceNumber="2026-114" status={{ label: "Past due", tone: "danger", description: "Payment was due on 20 September." }} currency="GBP" locale="en-GB" issuedOn="2026-09-01" dueOn="2026-09-20" billedTo={{ label: "Billed to", lines: ["Northwind Logistics", "24 Willow Lane", "London, N1 4AB"] }} billedFrom={{ label: "From", lines: ["Citrionus Ltd", "Registered in England 09876543"] }} lines={lines} subtotal={341250} taxes={[{ id: "vat", label: "VAT", detail: "VAT 20%", amount: 68250 }]} amountPaid={100000} amountDue={309500} amountDueNote="Payable within 30 days of issue." primaryAction={{ id: "pay", label: "Pay invoice", onClick: () => openPayment(), tone: "primary" }} actions={[{ id: "pdf", label: "Download PDF", href: "/invoices/2026-114.pdf" }]} />;

amountDue is required and supplied, never derived from the lines, so a rounding rule or a credit the block does not know about cannot silently change it.

Three different absences

SituationHow to express it
No invoice at allstate="empty" with emptyTitle / emptyDescription
An invoice carrying no lineslines={[]} with linesEmptyTitle / linesEmptyDescription
A filtered view with no matcheslines={[]} with your own no-matches wording in the same props
<InvoiceDetailBlock currency="GBP" amountDue={0} lines={[]} linesEmptyTitle="This invoice has no lines yet" linesEmptyDescription="Add a line to issue it." linesEmptyAction={{ id: "add", label: "Add a line", onClick: () => addLine() }} />

Long invoices

Beyond maxVisibleLines (default 12) the table body moves into a bounded, focusable, vertically scrolling region, so a long invoice cannot lengthen the page without limit.

At enlarged text sizes, table columns can scroll horizontally inside a named, keyboard-focusable area. The page itself stays within its container, and totals remain outside the scrolling table.

<InvoiceDetailBlock currency="GBP" amountDue={309500} lines={fortyLines} maxVisibleLines={12} scrollRegionMaxHeight="26rem" />

Unknown amounts and translation

<InvoiceDetailBlock currency="EUR" locale="nl-NL" amountDue={null} unknownLabel="Niet beschikbaar" lines={lines} linesLabel="Factuurregels" columnLabels={{ description: "Omschrijving", quantity: "Aantal", unitPrice: "Stukprijs", amount: "Bedrag", }} />

Loading, error and empty

<InvoiceDetailBlock currency="GBP" amountDue={null} lines={[]} state="loading" />
<InvoiceDetailBlock currency="GBP" amountDue={null} lines={[]} state="error" errorTitle="This invoice could not be loaded" onRetry={() => refetch()} />
<InvoiceDetailBlock currency="GBP" amountDue={null} lines={[]} state="empty" emptyTitle="No invoice selected" emptyAction={{ id: "choose", label: "Choose invoice", onClick: () => openList() }} />

Payment the viewer may not make

primaryAction is a FinanceAction, so a payment the viewer is not permitted to make renders disabled with the reason visible rather than as a dead button.

<InvoiceDetailBlock currency="GBP" amountDue={309500} lines={lines} primaryAction={{ id: "pay", label: "Pay invoice", unavailableReason: "Only a billing administrator can pay an invoice.", }} />

Props

PropTypeDefaultDescription
linesInvoiceLine[]—Required.
amountDueFinanceMoneyValue—Required. Supplied, never derived. null is unknown.
titleReactNode"Invoice"Block title.
headingLevel"h1"–"h6""h2"Element for title.
descriptionReactNode—Supporting sentence.
invoiceNumberstring—Shown above the title and in the metadata list.
statusFinanceStatus—{ label, tone?, description? }.
currencystring"GBP"ISO 4217 code shared by every amount.
localestring"en-GB"Keeps server and client formatting identical.
unknownLabelstring"Not available"Words shown wherever an amount is null.
timeZonestring—IANA zone for the invoice dates.
formatDate(value: Date) => string—Formatting escape hatch.
issuedOnFinanceDateValue—Issue date.
dueOnFinanceDateValue—Due date.
issuedOnLabelstring"Issued"Label override.
dueOnLabelstring"Due"Label override.
metaArray<{ id, label, value, icon? }>—Further metadata rows, e.g. purchase order, tax id.
billedToInvoiceParty—{ label?, lines }.
billedFromInvoiceParty—{ label?, lines }.
linesLabelstring"Invoice lines"Names the table and its scroll region.
columnLabels{ description?, quantity?, unitPrice?, amount? }—Column headers, for translation.
maxVisibleLinesnumber12Beyond this the body becomes a bounded scroll region.
scrollRegionMaxHeightstring"26rem"Height of that region.
linesEmptyTitlestring—Wording for an invoice with no lines.
linesEmptyDescriptionReactNode—Wording for an invoice with no lines.
linesEmptyActionFinanceAction—Recovery step on that panel.
subtotalFinanceMoneyValue—Before taxes and adjustments.
subtotalLabelstring"Subtotal"Label override.
taxesInvoiceAdjustment[]—Tax lines applied after the subtotal.
adjustmentsInvoiceAdjustment[]—Credits, discounts and other adjustments.
amountPaidFinanceMoneyValue—Already settled. undefined omits the row.
amountPaidLabelstring"Paid to date"Label override.
amountDueLabelstring"Amount due"Label override.
totalsLabelstring"Invoice totals"Accessible name for the totals list.
amountDueNoteReactNode—One line under the amount due, e.g. payment terms.
primaryActionFinanceAction—The dominant action. Omitted entirely when absent.
actionsFinanceAction[]—Supporting actions.
actionsLabelstring"Invoice actions"Accessible name for the action group.
noteReactNode—Terms, remittance details or notes. Rendered last.
state"default" | "loading" | "empty" | "error""default"Lifecycle owned by your application.
loadingMessagestring"Loading invoice"Announced while loading.
errorTitlestring"This invoice could not be loaded"Shown while state="error".
errorDescriptionReactNode—Shown while state="error".
onRetry() => void—The retry control is omitted entirely without it.
emptyTitlestring"No invoice selected"Shown while state="empty".
emptyDescriptionReactNode—Shown while state="empty".
emptyActionFinanceAction—Recovery step on the empty panel.
classNamestring—Lands on the container root, outside the card padding.

InvoiceLine

MemberTypeDescription
idstringRequired, stable.
descriptionReactNodeRequired.
detailReactNodeA second line, e.g. a period or a SKU.
quantitynumber | nullDecimals allowed, e.g. 7.5 hours. null is unknown.
quantityUnitstringUnit quantity is measured in.
unitPriceFinanceMoneyValueMinor-unit integer, or null for unknown.
amountFinanceMoneyValueRequired. Negative is a credit line.

InvoiceAdjustment

{ id, label, amount, detail? }. A negative amount is a credit or discount; detail carries the rate or basis, e.g. “VAT 21%”.

InvoiceParty

{ label?, lines }, where lines are address or identity lines in reading order.

States

  • default — the invoice.
  • loading / error — with an optional retry control.
  • empty — no invoice at all.
  • lines empty — an invoice carrying no lines, or a filtered view with no matches, in your own wording.
  • unknown amount — per amount, from null.
  • payment unavailable — a permission-blocked primaryAction.

Verification

No verification run exists for this block yet. The batch that built it had its run interrupted and is repeating it, and its batch report (docs/blocks-batch-d.md) did not exist when this page was written. This page was reconciled against the block’s public TypeScript types and its 14 story exports, and the block resolves from a built package with its types — nothing more.

Not verified: layout at any width, contrast in either mode, keyboard flows, theme overrides, a screen-reader pass, any non-Chromium engine, operating-system text-size settings, or physical devices. The bounded line-scroll region in particular has not been measured.

Known limitations

  • amountDue is supplied, never derived from the lines.
  • The block takes no payment. primaryAction hands that to your application.
  • Every amount in one invoice shares a single currency and locale.
Last updated on