Invoice Page React block for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React invoice page to edit and view Romanian B2B invoices: client, series, VAT lines, totals, an A4 preview, sending and e-Factura.
Switch between Edit and Preview in the header (arrow keys move the segmented control). In edit mode pick the client in the search field (name, CUI or city; a full-height sheet on phones), or type a new name and choose New client to open a dialog where Look up in ANAF (or Enter in the CUI field) fills the company from its CUI; Change swaps the client. Pick the series (the number jumps to the next free one), the issue date (the due date keeps the same term), the due date with the +15, +30 and +45 days presets, and the currency: EUR converts every price at the BNR rate and shows the RON equivalent under the total. Edit the lines like a spreadsheet (product search, quantity, price, VAT rate, discount, drag or Move up / Move down to reorder, remove with undo; cards with an edit sheet on phones) and type the notes. The aside keeps the summary with VAT per rate and the total, the IBAN with a copy button, and the e-Factura switch in view while you scroll (on a laptop with a sidebar the lines take the full width under them instead). A draft saves itself 800 ms after the last change (Saving…, then Saved a few seconds ago); Ctrl or Cmd + S saves at once. Issue invoice checks the client, its CUI, the number (a taken one fails), the dates and the lines: the problems are listed in an alert whose links move focus to each field, and the lines get their own errors. Issuing uploads to SPV when the switch is on and turns to the preview: an A4 sheet in paper colours in both themes, scaled to the width on tablets and phones (pinch to zoom). Send opens the send dialog (a full-height sheet on phones): recipients as chips, CC, subject, message, the PDF attached and an SPV switch; Ctrl or Cmd + Enter sends. Download PDF opens the print dialog, which prints only the document. The ⋯ menu holds Duplicate (a new draft with the next number), Mark as paid (the badge pops), Issue credit note and Void invoice (undo in a toast for 5 seconds). Overdue invoices offer Send reminder; paid and cancelled ones are read-only. On phones a bar at the bottom shows the total with Issue, Send or Download.
Invoice Page is a Pro React block in the React blocks category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add invoice-page, together with Alert, Badge, Button, Combobox, Copy Button and 28 more and the npm packages motion, lucide-react, react-hook-form and zod.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add invoice-pageInvoice Page is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import InvoicePage from @/components/blocks/invoice-page.
// app/(admin)/invoices/[id]/page.tsx (the shell lives in app/(admin)/layout.tsx: <AdminShell>{children}</AdminShell>)
"use client"
import { InvoicePage, type InvoiceDocument } from "@/components/blocks/invoice-page"
export default function InvoiceRoute() {
return (
<InvoicePage
onSave={async (invoice: InvoiceDocument) => {
await fetch(`/api/invoices/${invoice.id}`, { method: "PUT", body: JSON.stringify(invoice) })
}}
onIssue={async (invoice) => {
await fetch(`/api/invoices/${invoice.id}/issue`, { method: "POST" })
}}
onSend={async (invoice, payload) => {
await fetch(`/api/invoices/${invoice.id}/send`, { method: "POST", body: JSON.stringify(payload) })
}}
/>
)
}Props
invoiceInvoiceDocumentThe document, controlled ({ id, series, number, status, issueDate, dueDate, currency, client, supplier, lines, notes, paymentTerms, documentDiscount, sentToSpv, paidAt, issuedBy }); pair it with onInvoiceChange.
defaultInvoiceInvoiceDocumentThe document while uncontrolled; defaults to INVOICE_PAGE_SAMPLE (draft CRP #2026-0412 to Tech Solutions SRL). createInvoiceDraft(now) gives an empty one.
onInvoiceChange(invoice: InvoiceDocument) => voidCalled with the whole document after every edit and status change.
mode"edit" | "preview"The view, controlled; pair it with onModeChange.
defaultModedefault"edit""edit" | "preview"The first view while uncontrolled; when left out, edit for drafts and preview for every other status (the sample is a draft).
onModeChange(mode: InvoiceMode) => voidCalled when the segmented control, issuing or duplicating switches the view.
statusdefault"draft""draft" | "issued" | "sent" | "paid" | "overdue" | "cancelled"Seeds the document's status on mount (a playground shortcut; the sample is a draft). Overdue moves the dates 3 days past due, paid sets today as the payment date.
clientsInvoiceParty[]The client picker's options ({ id, name, cui, regCom, address, city, county, email, vatPayer, individual }); defaults to the 12 Northwind CRM clients (INVOICE_PAGE_CLIENTS).
productsInvoiceProduct[]The line product search; defaults to the 7 Northwind CRM products and services (INVOICE_PAGE_PRODUCTS).
productCurrencydefault"RON"stringThe currency of the product prices; they are converted to the document's currency through exchangeRate.
supplierInvoicePartyThe issuer printed on the document and shown in Payment; defaults to Northwind Soft SRL (RO40528170, Banca Transilvania).
seriesstring[]The document series; defaults to ["CRP", "CRPX"] (labels.seriesHints describes each).
takenNumbersRecord<string, string[]>Numbers already used per series: the next free one is suggested and a taken one fails validation; defaults to INVOICE_PAGE_TAKEN_NUMBERS.
exchangeRate{ currency: string; rate: number; date: Date; source?: string }The foreign currency the Currency field offers and its RON rate; defaults to BNR 1 EUR = 4.9713 RON on 2026-09-24.
userNamedefault"Emma Carter"stringWho signs: Prepared by on the document and the sender of the email, unless the invoice has issuedBy.
pricesIncludeVatdefaultfalsebooleanThe price column holds prices with VAT (B2C); totals are computed backwards.
eFacturabooleanThe Send to SPV automatically on issue switch, controlled.
defaultEFacturadefaulttruebooleanThe SPV switch while uncontrolled: issuing uploads the invoice to e-Factura and marks it Sent to SPV.
onEFacturaChange(on: boolean) => voidCalled when the SPV switch flips.
spvConnecteddefaulttruebooleanAn ANAF certificate is linked; false disables the SPV switches and says Neconectat.
onSave(invoice: InvoiceDocument) => (void | Promise<unknown>)Saves the draft, 800 ms after the last change and on Ctrl or Cmd + S; simulated by default. A rejection shows Nu am putut salva.
onIssue(invoice: InvoiceDocument) => (void | Promise<unknown>)Issues a valid draft (blank lines removed); simulated by default. Then the status becomes Issued, or Sent to SPV with e-Factura on.
onSend(invoice: InvoiceDocument, payload: { to; cc; subject; message; spv }) => (void | Promise<unknown>)Sends from the dialog (a draft is issued first); simulated by default. A rejection shows its message in the dialog.
onDownload(invoice: InvoiceDocument) => voidDownload PDF; default window.print(), where Save as PDF gives a real PDF with only the document on it.
onMarkPaid(invoice: InvoiceDocument) => (void | Promise<unknown>)Mark as paid; simulated by default, then the status is Paid with today's date.
onDuplicate(invoice: InvoiceDocument) => (void | Promise<unknown>)Duplicate; simulated by default, then the page opens a new draft with the next number and today's dates.
onStorno(invoice: InvoiceDocument) => (void | Promise<unknown>)Emite storno for issued, sent, paid and overdue invoices; simulated by default with a toast.
onCancel(invoice: InvoiceDocument) => (void | Promise<unknown>)Void invoice: the status changes at once with an undo toast; a rejection rolls it back.
onRemind(invoice: InvoiceDocument) => (void | Promise<unknown>)Send reminder on overdue invoices; simulated by default with a toast.
onCreateClient(client: InvoiceParty) => (void | InvoiceParty | Promise<void | InvoiceParty>)Saves a client from the Client nou dialog; a returned party replaces the typed one. Simulated by default; the new client is selected.
linesLayoutdefault"auto""auto" | "table" | "cards"The lines editor: auto shows the spreadsheet table wherever it gets 936px (from a page about 936px wide the lines span the full row under the client and the summary, from 1352px they sit beside the sticky summary), cards with an edit sheet below.
stickyBardefaulttruebooleanThe phone bar (below 768px) with the total and Issue, Send or Download; hide the shell's tab bar while it shows.
defaultSendOpendefaultfalsebooleanOpens the send dialog on mount (examples).
defaultShowErrorsdefaultfalsebooleanStarts with the issue checks shown, as after pressing Emite factura (examples).
saveShortcutdefault"mod+s"stringSaves the draft; "" turns it off.
sendShortcutdefault"mod+enter"stringSends from the open send dialog, also while typing the message; "" turns it off.
invoicesHrefdefault"/invoices"stringWhere the breadcrumb's Facturi goes.
labelsPartial<InvoicePageLabels>Every visible string, English by default (DEFAULT_INVOICE_PAGE_LABELS; INVOICE_PAGE_LABELS_EN is the same set under its older name). Nested keys pass strings to the composed items (lineItems, validation, datePicker, tagInput, stickyBar).
localedefault"en-US"stringIntl locale for money, numbers and dates.
currencydefault"RON"stringSeeds the document's currency on mount (the sample is in RON); EUR converts the prices at exchangeRate.
nowDate | numberThe reference time for today, overdue days, payments and duplicates; default the live clock.
tonedefault"neutral"ToneThe accent of the primary actions, the mode switch, the date pickers and the switches.
radiusdefault"md""sm" | "md" | "lg"The panels' radius: 12, 16 or 24px.
loadingdefaultfalsebooleanThe header, panels and aside as skeletons in the real layout.
animateIndefaulttruebooleanThe header and sections rise in once on mount; off in thumbnails and under reduced motion.
linkComponent(props: LinkComponentProps) => ReactNodeThe router link for the breadcrumb; default an <a> (or the LinkProvider's).
classNamestringClasses for the root.
classNamesPartial<Record<InvoicePageSlot, string>>Classes for header, alerts, edit, main, client, details, lines, notes, aside, summary, payment, efactura, preview, document, send and bar.
Parts
DEFAULT_INVOICE_PAGE_LABELS
constantThe English strings for every visible label, the default of labels. Type: InvoicePageLabels.
INVOICE_PAGE_LABELS_EN
constantThe same English strings under their older name, kept for existing imports.
INVOICE_PAGE_SAMPLE
constantThe demo invoice: draft CRP 2026-0412 from Northwind Soft SRL in RON, four lines at 21% and 11% VAT.
INVOICE_PAGE_SAMPLE_EN
constantThe same invoice in EUR with converted prices and an English payment note.
INVOICE_PAGE_SUPPLIER
constantThe sample supplier (Northwind Soft SRL) with its CUI, registry number, address and bank account.
INVOICE_PAGE_CLIENTS
constantThe sample clients offered by the client picker.
INVOICE_PAGE_ANAF_DIRECTORY
constantThe companies the demo's CUI lookup finds, standing in for the ANAF registry.
INVOICE_PAGE_PRODUCTS
constantThe product catalog for the line picker, priced in RON with their VAT rates and SKUs.
INVOICE_PAGE_PRODUCTS_EN
constantThe same catalog priced in EUR.
INVOICE_PAGE_SERIES
constantThe invoice series of the sample supplier: ["CRP", "CRPX"].
INVOICE_PAGE_TAKEN_NUMBERS
constantNumbers already used per series; the next free one is suggested and a taken one fails validation.
INVOICE_PAGE_EXCHANGE_RATE
constantThe sample BNR rate for EUR (4.9713 RON on Sep 24, 2026), shown on foreign currency invoices.
INVOICE_PAGE_NOW
constantThe demo clock (Sep 24, 2026, 4:40 PM), so due dates and overdue days stay stable.
createInvoiceDraft
functionA new draft: (now?, { series, number, supplier, currency, issuedBy }?) => InvoiceDocument with no client and one empty line.
invoiceWithStatus
functionMoves an invoice to a status with matching dates: (invoice, status, now?) => InvoiceDocument (overdue, paid, sent).
InvoicePageProps
typeProps of InvoicePage, including the div attributes of the root.
InvoiceDocument
typeThe invoice: series, number, status, dates, currency, client, supplier, lines, notes and discount.
InvoiceSendPayload
typeWhat onSend receives with the invoice: { to, cc, subject, message, spv }, spv sending it to e-Factura.
Examples
Issued, previewed as the A4 document
status="issued", tone="brand": the printable sheet, scaled to fit on phones.
Send dialog open
defaultSendOpen: recipients, subject and message prefilled; Ctrl or Cmd + Enter sends.
A new draft, Issue pressed
No client, one empty line and a taken number: every error listed and linked to its field.
Paid
status="paid" in edit mode: the success alert and read-only lines.
Overdue
status="overdue": due 3 days ago, with Send reminder.
Loading
loading: header, panels and aside as skeletons in the real layout.
Export invoice in euro
locale="en-GB", an EUR invoice priced from the euro catalogue, with the RON equivalent at the BNR rate.
Dependencies
Files
- components/blocks/invoice-page.tsx
- components/blocks/invoice-page-data.ts
FAQ
Is the React Invoice Page block free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Invoice Page?
Run npx wingo-ui@latest add invoice-page. It also adds the Wingo UI items it builds on (Alert, Badge, Button, Combobox, Copy Button and 28 more) and the npm packages motion, lucide-react, react-hook-form and zod. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/invoice-page.
How does Invoice Page behave on phones?
In edit mode pick the client in the search field (name, CUI or city; a full-height sheet on phones), or type a new name and choose New client to open a dialog where Look up in ANAF (or Enter in the CUI field) fills the company from its CUI; Change swaps the client.
See also
More React blocks
Articles using this component
- React Invoice Template: Line Items, VAT and Print View
Build it: screens and apps10 min read
- E-Factura VAT Calculation in TypeScript and React
Build it: screens and apps11 min read