Nasaq

InvoiceView

A printable invoice document with parties, line items, tax, discount, payments and balance, plus download, print and pay actions.

PreviewOpen ↗

Code

import { InvoiceView, type InvoiceData } from "@fadymondy/nasaq/web";const invoice: InvoiceData = {  number: "INV-2026-0042",  status: "open",  issueDate: "2026-09-01",  dueDate: "2026-09-30",  currency: "USD",  taxRate: 0.15,  from: { name: "Nasaq Ltd", address: ["1 King Fahd Rd", "Riyadh"], taxId: "300000000000003" },  to: { name: "Acme Co", email: "[email protected]" },  lines: [{ id: "1", description: "Team plan", details: "Sep 2026", quantity: 5, unitPrice: 49 }],};export function Invoice() {  return <InvoiceView invoice={invoice} />;}

Billing · beta

Live examples and controls: InvoiceView in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/invoice-view.json

Renders one invoice as a document: issuer and customer, dates, line items, subtotal, discount, tax, total, payments received and the balance due. It computes the totals from the lines with computeInvoice, so the numbers always add up. It prints cleanly: on print only the invoice sheet is visible, in black on white, and rows do not split across pages.

When to use

  • Showing a single invoice in an app or customer portal.
  • Letting people print an invoice or save it as a PDF from the browser's print dialog.

When not to use

Import

import { InvoiceView, type InvoiceData } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { InvoiceView, type InvoiceData } from "@fadymondy/nasaq/web";

const invoice: InvoiceData = {
  number: "INV-2026-0042",
  status: "open",
  issueDate: "2026-09-01",
  dueDate: "2026-09-30",
  currency: "USD",
  taxRate: 0.15,
  from: { name: "Nasaq Ltd", address: ["1 King Fahd Rd", "Riyadh"], taxId: "300000000000003" },
  to: { name: "Acme Co", email: "[email protected]" },
  lines: [{ id: "1", description: "Team plan", details: "Sep 2026", quantity: 5, unitPrice: 49 }],
};

export function Invoice() {
  return <InvoiceView invoice={invoice} />;
}

Anatomy

InvoiceView                       data-slot="invoice-view"
├─ action bar                     Download · Print · Pay now (hidden in print)
└─ sheet                          data-slot="invoice-sheet" (the only thing printed)
   ├─ header                      logo, number, InvoiceStatusBadge
   ├─ parties                     from, to, dates
   ├─ lines table                 description, quantity, unit price, amount
   ├─ totals                      subtotal, discount, tax, total, paid, due
   └─ notes and terms

API

InvoiceView

InvoiceViewProps extends Omit<ComponentProps<"section">, "children" | "title">.

PropTypeDefaultDescription
invoiceInvoiceDatarequiredThe invoice.
onDownload?() => Promise<void>noneBuilds and saves the PDF (yours). Omitted: the button is hidden; Print still works.
onPrint?() => voidwindow.print()Replaces the print action.
onPay?() => voidnoneShows "Pay now" while the invoice is open or overdue.
actions?ReactNodenoneExtra buttons before the built-in ones.
hideActions?booleanfalseHides the whole action bar.
labels?InvoiceLabelsbuilt-in en/arPartial string overrides.

InvoiceData

number, status: InvoiceStatus ("draft" | "open" | "paid" | "overdue" | "void" | "refunded"), issueDate, dueDate?, currency (ISO 4217), from and to (InvoiceParty: name, address?: string[], email?, phone?, taxId?, logo?), lines (InvoiceLine: id, description, details?, quantity, unitPrice, taxRate?), discount? (fixed amount), taxRate? (fraction), payments? (InvoicePayment: id, date, method, amount), notes?, terms?.

computeInvoice

(invoice: Pick<InvoiceData, "lines" | "discount" | "taxRate" | "payments" | "status">) => InvoiceTotals. Returns { subtotal, discount, tax, total, paid, due }. The discount is spread over the lines in proportion; due is 0 for draft, void and refunded invoices.

InvoiceStatusBadge

PropTypeDefaultDescription
statusInvoiceStatusrequiredThe status; shows an icon and a word.
labels?InvoiceLabelsbuilt-inString overrides.

Other props go to the underlying Badge. useInvoiceStrings(labels?) returns { t, ar, locale } for sibling components.

Examples

<InvoiceView
  invoice={{ ...invoice, status: "paid", payments: [{ id: "p1", date: "2026-09-05", method: "Visa ending 4242", amount: 281.75 }] }}
/>

Download and pay

<InvoiceView
  invoice={invoice}
  onDownload={async () => {
    await savePdf(invoice.number);
  }}
  onPay={() => router.push("/pay")}
/>

Arabic

<NasaqProvider locale="ar">
  <InvoiceView invoice={{ ...invoice, currency: "SAR" }} />
</NasaqProvider>

Accessibility

KeyAction
TabMove through the action buttons.
Enter / SpaceActivate Download, Print or Pay now.
  • The lines are a real table with column headers; totals are a description list.
  • The status badge carries an icon and a word, never colour alone.
  • Localise labels for any custom copy.

RTL & i18n

Built-in English and Arabic. Amounts use Intl with the invoice currency, Latin digits by default like Num, isolated left to right. Invoice numbers, tax ids and emails stay left to right inside Arabic text. Dates use Intl.DateTimeFormat.

Styling & tokens

Print rules are injected with the component: under @media print everything except [data-slot="invoice-sheet"] is hidden, colours are forced to black on white, and break-inside: avoid keeps rows whole. Screen styling uses bg-card, border-border and text tokens. Extend with className; do not use raw hex.

Do / Don't

  • Do provide your own PDF through onDownload; the browser print dialog also offers "Save as PDF".
  • Do pass logo as an element and leave it untouched.
  • Don't compute totals yourself for display; use computeInvoice so the sheet and your data agree.

Lab

https://docs.nasaqui.com/?path=/docs/components-billing-invoiceview--docs

On this page