Invoice Preview UI — Free HTML CSS Snippet

Invoice Preview · Cards · Plain HTML & CSS · Live preview

Share & Support

What's included

Features

Semantic HTML table for line items with correct column alignment
text-align: right + padding-right on numeric columns for clean alignment
font-variant-numeric: tabular-nums on amount cells for stable digit width
@media print: hides button, removes background and border-radius for clean PDF output
From/Bill To parties in 2-column CSS Grid
Totals block with subtotal, tax, discount, and Total Due row
Status chip (Unpaid/Paid/Overdue) with color-coded background
window.print() PDF generation: no library dependency for basic use

About this UI Snippet

Invoice Preview — Line Items Table, Tax, Discount, Totals, and Print to PDF

Screenshot of the Invoice Preview snippet rendered live

An invoice preview component is a fundamental element in any freelance tool, SaaS billing system, or e-commerce back office — at checkout, it is preceded by the order summary. This snippet renders a complete print-ready invoice document with a branded header, from/to party addresses, a multi-column line items table with description, quantity, rate, and amount columns, a totals block with subtotal, tax, discount, and total due, a payment terms + bank details footer, and a Print / Save PDF button that calls window.print().

The layout structure

The invoice is a single white-background .invoice container with max-width 720px — the standard printable content width. It is divided into semantic sections: .inv-head (brand + invoice number + dates), .parties grid, .items-table, .totals-wrap, .inv-footer (payment terms + bank), and .inv-bottom (thank-you + print button). Each section has a border-bottom: 1px solid separator.

The line items table

The items use a standard HTML table (not CSS Grid) for semantic correctness and natural column alignment — make the rows editable with the editable table pattern. The Qty, Rate, and Amount columns use text-align: right with padding-right: 32px to align numbers with the invoice edges. font-variant-numeric: tabular-nums on numeric cells prevents amount digits from jumping as values change.

Print to PDF via window.print()

The Print button calls window.print(). A @media print CSS block hides the button and removes the background and border-radius from the invoice container, producing a clean white printed document. Users can Save as PDF from the browser print dialog. For programmatic PDF generation, use libraries like jsPDF or Puppeteer.

The totals block

The totals are right-aligned in a 240px-wide column (matching the Amount column width). Subtotal, Tax (8%), and Discount rows use the same flex justify-content: space-between pattern. The Total Due row uses a bold font and a border-top separator to visually separate it from the line items.

Dynamic totals with JavaScript

To compute totals from the line items dynamically, iterate all item rows and extract qty and rate values: const rows = document.querySelectorAll(".item-row"); let subtotal = 0; rows.forEach(row => { const qty = parseFloat(row.querySelector(".item-qty").textContent) || 0; const rate = parseFloat(row.querySelector(".item-rate").textContent.replace(/[^0-9.]/g, "")) || 0; subtotal += qty * rate; }). Then compute tax as subtotal * TAX_RATE and discount as a fixed value or percentage. Write results to the .totals span elements. This approach makes the invoice fully dynamic — add or remove rows and the totals recalculate automatically.

Programmatic PDF generation beyond window.print()

For more control over the PDF output — custom page size, headers, footers, or watermarks — use a server-side approach with Puppeteer or headless Chrome: render the invoice HTML on the server, call page.pdf({ format: "A4", printBackground: true }) and stream the result as a PDF download. Client-side alternatives include jsPDF with html2canvas (converts the DOM to a canvas then to PDF) or the PDF.js render pipeline. For invoices requiring digital signatures, use a PDF library that supports PDF/A format and signature fields such as pdf-lib on Node.js.

Step by step

How to Use

  1. 1
    Update the invoice detailsEdit the brand name, logo SVG, invoice number (#INV-2024-089), issue date, due date, and status chip. Change the From and Bill To party names, emails, and addresses.
  2. 2
    Edit the line itemsEach .item-row in the table contains a description, subtitle, qty, rate, and amount. Update these values. To add a new line, duplicate an .item-row and update its content.
  3. 3
    Update the totalsEdit the Subtotal, Tax (change the rate label and value), Discount, and Total Due values in the .totals block. These are hardcoded strings — for a dynamic invoice, compute them with JS from the items array.
  4. 4
    Print or save as PDFClick the Print / Save PDF button. In the browser print dialog, select "Save as PDF" as the destination. The @media print CSS hides the button and removes decorative styles for a clean printout.
  5. 5
    Make totals dynamicReplace the static totals with JS: read qty and rate from each row, compute line amounts and subtotal, apply tax rate and discount, and write the results to the .totals span elements.
  6. 6
    Export for your frameworkClick "JSX" to download a React InvoicePreview component that accepts an invoice prop object. Click "Vue" for a Vue 3 SFC. The JSX version separates the line items into a LineItem sub-component.

Real-world uses

Common Use Cases

Freelance invoice generator and PDF export
Pair with FWD Tools' own invoice generator. Accept invoice data as a prop object, render this component in a modal or full page, and call window.print() to save as PDF. Connect to localStorage to persist draft invoices.
SaaS billing portal invoice history view
Render a list of historical invoices in a billing dashboard. Each invoice row in the list navigates to this invoice preview page. The Print button lets users download any past invoice as a PDF for their expense records.
E-commerce order confirmation and receipt display
Adapt the line items table to show ordered products instead of services. Replace the Rate column with Unit Price. Remove Tax and Discount if not applicable. The parties section becomes Shipped To and Order Number.
Generate dynamic invoices from a database
Fetch invoice data from an API endpoint (/api/invoices/:id) and populate the component props. Compute subtotal with items.reduce((sum, i) => sum + i.qty * i.rate, 0). Apply tax and discount multipliers. Display the formatted Total Due.
Study print CSS and @media print techniques
The @media print block demonstrates standard print CSS patterns: hiding interactive elements (buttons, navigation), removing backgrounds and shadows, and setting max-width for printable content. This technique works for any printable UI — receipts, reports, certificates, and PDFs.
Contract and proposal preview alongside invoice
Use the same card layout for a project proposal template: replace the line items table with a scope-of-work list, remove the bank details, and change the footer to a signature block. The same print CSS makes proposals saveable as PDFs.

Got questions?

Frequently Asked Questions

Click the Print / Save PDF button (or press Ctrl+P / Cmd+P). In the browser print dialog, set the Destination to "Save as PDF". The @media print CSS removes the background, border-radius, and button so the PDF looks like a clean white document.

Read each row: const items = [...document.querySelectorAll(".item-row")].map(r => ({ qty: parseFloat(r.cells[1].textContent), rate: parseFloat(r.cells[2].textContent.replace(/[^0-9.]/g,"")) })); const subtotal = items.reduce((s, i) => s + i.qty * i.rate, 0); Then apply taxRate and discount to compute the total.

Build an InvoicePreview component that accepts an invoice prop: { number, issueDate, dueDate, from, to, items[], taxRate, discount }. Compute subtotal with items.reduce((sum, i) => sum + i.qty * i.rate, 0). Apply tax as subtotal * taxRate and subtract the discount. Map items to table row elements. The print button calls window.print() directly — no extra library needed for basic PDF output. For a print preview mode, render the InvoicePreview inside a React Portal in a separate div with print-only CSS (@media not print { display: none }) so the preview can be shown in a modal on screen while the rest of the app remains hidden when the user triggers print.

No — the invoice is pure HTML and CSS. The line items, dates, and totals are static markup, which is exactly what you want when the invoice is a render target: your server template or framework loops real line items into the rows and prints computed totals into the summary cells. Because there is no runtime dependency, the same markup works in a print stylesheet, a PDF renderer like Puppeteer or wkhtmltopdf, and an email-safe variant with inlined styles. The React, Vue, and Angular exports give you the component shell to feed props into.

The logo is an inline SVG next to a .brand-name span — swap the SVG paths for your own mark (keep it around 28×28 with a rounded rect background) and edit the name text. The indigo accent used on the logo, the INVOICE badge, and the totals highlight is a hex value repeated in the CSS, so a find-and-replace on it rebrands the whole document; move it into a CSS custom property like --inv-accent if you theme invoices per client.

Invoice Preview — Export as HTML, React, Vue, Angular & Tailwind

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Invoice Preview</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, sans-serif; background: #f1f5f9; min-height: 100vh; padding: 32px 20px; display: flex; justify-content: center; }
    .page { width: 100%; }
    .invoice { background: #fff; border-radius: 16px; max-width: 720px; margin: 0 auto; box-shadow: 0 4px 24px rgba(0,0,0,0.08); overflow: hidden; }
    .inv-head { background: #f8fafc; padding: 28px 32px; border-bottom: 1px solid #e2e8f0; }
    .inv-brand { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; }
    .logo { display: flex; align-items: center; gap: 10px; }
    .brand-name { font-size: 18px; font-weight: 800; color: #1e293b; }
    .inv-meta { text-align: right; }
    .inv-badge { font-size: 10px; font-weight: 800; letter-spacing: 2px; color: #6366f1; background: rgba(99,102,241,0.08); border-radius: 6px; padding: 3px 8px; display: inline-block; margin-bottom: 4px; }
    .inv-num { font-size: 14px; font-weight: 600; color: #475569; }
    .inv-dates { display: flex; gap: 24px; flex-wrap: wrap; }
    .date-row { display: flex; flex-direction: column; gap: 3px; }
    .date-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
    .date-val { font-size: 13px; font-weight: 600; color: #334155; }
    .date-val.due { color: #ef4444; }
    .status-chip { display: inline-block; background: #fef3c7; color: #92400e; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 20px; }
    .parties { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px 32px; border-bottom: 1px solid #f1f5f9; }
    .party-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
    .party-name { font-size: 15px; font-weight: 700; color: #1e293b; margin-bottom: 4px; }
    .party-detail { font-size: 12px; color: #64748b; line-height: 1.7; }
    .items-table { width: 100%; border-collapse: collapse; padding: 0 32px; display: table; }
    .items-table { padding: 0; }
    .items-table th, .items-table td { padding: 12px 16px; }
    .items-table thead tr { border-bottom: 2px solid #f1f5f9; }
    .th-desc { text-align: left; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-left: 32px; }
    .th-num { text-align: right; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-right: 32px; }
    .item-row { border-bottom: 1px solid #f8fafc; }
    .item-row td:first-child { padding-left: 32px; }
    .item-row td:last-child { padding-right: 32px; }
    .item-title { font-size: 14px; font-weight: 600; color: #1e293b; }
    .item-sub { font-size: 12px; color: #94a3b8; margin-top: 2px; }
    .num { text-align: right; font-size: 14px; color: #475569; font-variant-numeric: tabular-nums; }
    .num.bold { font-weight: 700; color: #1e293b; }
    .totals-wrap { display: flex; justify-content: flex-end; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; }
    .totals { width: 240px; display: flex; flex-direction: column; gap: 8px; }
    .tot-row { display: flex; justify-content: space-between; font-size: 13px; color: #64748b; }
    .discount { color: #16a34a; }
    .tot-final { display: flex; justify-content: space-between; font-size: 17px; font-weight: 800; color: #1e293b; padding-top: 10px; border-top: 2px solid #e2e8f0; margin-top: 4px; }
    .inv-footer { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; background: #fafafa; }
    .note-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
    .note-text { font-size: 12px; color: #64748b; line-height: 1.6; }
    .inv-bottom { display: flex; justify-content: space-between; align-items: center; padding: 16px 32px; }
    .inv-bottom span { font-size: 13px; color: #94a3b8; font-style: italic; }
    .print-btn { display: flex; align-items: center; gap: 6px; background: #1e293b; color: #fff; border: none; padding: 9px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: background 0.15s; }
    .print-btn:hover { background: #0f172a; }
    @media print { body { background: #fff; padding: 0; } .invoice { box-shadow: none; border-radius: 0; } .print-btn { display: none; } }
  </style>
</head>
<body>
  <div class="page">
    <div class="invoice">
      <div class="inv-head">
        <div class="inv-brand">
          <div class="logo">
            <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="7" fill="#6366f1"/><path d="M8 20L14 8l6 12" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 16h8" stroke="white" stroke-width="2.5" stroke-linecap="round"/></svg>
            <span class="brand-name">Acme Studio</span>
          </div>
          <div class="inv-meta">
            <div class="inv-badge">INVOICE</div>
            <div class="inv-num">#INV-2024-089</div>
          </div>
        </div>
        <div class="inv-dates">
          <div class="date-row"><span class="date-label">Issue Date</span><span class="date-val">June 1, 2024</span></div>
          <div class="date-row"><span class="date-label">Due Date</span><span class="date-val due">June 15, 2024</span></div>
          <div class="date-row"><span class="date-label">Status</span><span class="status-chip">Unpaid</span></div>
        </div>
      </div>
      <div class="parties">
        <div class="party">
          <div class="party-label">From</div>
          <div class="party-name">Acme Studio</div>
          <div class="party-detail">[email protected]</div>
          <div class="party-detail">San Francisco, CA 94105</div>
          <div class="party-detail">Tax ID: US-98765432</div>
        </div>
        <div class="party">
          <div class="party-label">Bill To</div>
          <div class="party-name">TechFlow Inc.</div>
          <div class="party-detail">[email protected]</div>
          <div class="party-detail">Austin, TX 78701</div>
          <div class="party-detail">PO: PO-2024-0041</div>
        </div>
      </div>
      <table class="items-table">
        <thead>
          <tr>
            <th class="th-desc">Description</th>
            <th class="th-num">Qty</th>
            <th class="th-num">Rate</th>
            <th class="th-num">Amount</th>
          </tr>
        </thead>
        <tbody>
          <tr class="item-row">
            <td>
              <div class="item-title">UI Design — Dashboard Screens</div>
              <div class="item-sub">Figma, 12 screens, 2 rounds of revision</div>
            </td>
            <td class="num">1</td>
            <td class="num">$3,200.00</td>
            <td class="num bold">$3,200.00</td>
          </tr>
          <tr class="item-row">
            <td>
              <div class="item-title">Frontend Development</div>
              <div class="item-sub">React + Tailwind, component library integration</div>
            </td>
            <td class="num">40 hr</td>
            <td class="num">$95.00</td>
            <td class="num bold">$3,800.00</td>
          </tr>
          <tr class="item-row">
            <td>
              <div class="item-title">Monthly Maintenance</div>
              <div class="item-sub">May 2024 — bug fixes, updates, monitoring</div>
            </td>
            <td class="num">1</td>
            <td class="num">$450.00</td>
            <td class="num bold">$450.00</td>
          </tr>
        </tbody>
      </table>
      <div class="totals-wrap">
        <div class="totals">
          <div class="tot-row"><span>Subtotal</span><span>$7,450.00</span></div>
          <div class="tot-row"><span>Tax (8%)</span><span>$596.00</span></div>
          <div class="tot-row"><span>Discount</span><span class="discount">&#x2212;$200.00</span></div>
          <div class="tot-final"><span>Total Due</span><span>$7,846.00</span></div>
        </div>
      </div>
      <div class="inv-footer">
        <div class="note">
          <div class="note-label">Payment Terms</div>
          <div class="note-text">Payment due within 14 days. Bank transfer or PayPal accepted. Late payments may incur a 2% monthly fee.</div>
        </div>
        <div class="bank">
          <div class="note-label">Bank Details</div>
          <div class="note-text">Bank: Chase &nbsp;&middot;&nbsp; Account: 0042-8812-09 &nbsp;&middot;&nbsp; Routing: 021000021</div>
        </div>
      </div>
      <div class="inv-bottom">
        <span>Thank you for your business!</span>
        <button class="print-btn" onclick="window.print()">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
          Print / Save PDF
        </button>
      </div>
    </div>
  </div>
</body>
</html>
Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Invoice Preview</title>
  <!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    * {
      box-sizing: border-box; margin: 0; padding: 0;
    }

    body {
      font-family: system-ui, sans-serif; background: #f1f5f9; min-height: 100vh; padding: 32px 20px; display: flex; justify-content: center;
    }

    .date-val.due {
      color: #ef4444;
    }

    .items-table th, .items-table td {
      padding: 12px 16px;
    }

    .items-table thead tr {
      border-bottom: 2px solid #f1f5f9;
    }

    .item-row td:first-child {
      padding-left: 32px;
    }

    .item-row td:last-child {
      padding-right: 32px;
    }

    .num.bold {
      font-weight: 700; color: #1e293b;
    }

    .inv-bottom span {
      font-size: 13px; color: #94a3b8; font-style: italic;
    }

    body {
      background: #fff; padding: 0;
    }
  </style>
</head>
<body>
  <div class="w-full">
    <div class="bg-[#fff] rounded-2xl max-w-[720px] my-0 mx-auto shadow-[0_4px_24px_rgba(0,0,0,0.08)] overflow-hidden shadow-none rounded-none">
      <div class="bg-[#f8fafc] py-7 px-8 border-b border-b-[#e2e8f0]">
        <div class="flex justify-between items-start mb-[18px]">
          <div class="flex items-center gap-2.5">
            <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="7" fill="#6366f1"/><path d="M8 20L14 8l6 12" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 16h8" stroke="white" stroke-width="2.5" stroke-linecap="round"/></svg>
            <span class="text-lg font-extrabold text-[#1e293b]">Acme Studio</span>
          </div>
          <div class="text-right">
            <div class="text-xs font-extrabold tracking-[2px] text-[#6366f1] bg-[rgba(99,102,241,0.08)] rounded-md py-[3px] px-2 inline-block mb-1">INVOICE</div>
            <div class="text-sm font-semibold text-[#475569]">#INV-2024-089</div>
          </div>
        </div>
        <div class="flex gap-6 flex-wrap">
          <div class="flex flex-col gap-[3px]"><span class="text-xs font-semibold text-[#94a3b8] uppercase tracking-[0.5px]">Issue Date</span><span class="date-val text-[13px] font-semibold text-[#334155]">June 1, 2024</span></div>
          <div class="flex flex-col gap-[3px]"><span class="text-xs font-semibold text-[#94a3b8] uppercase tracking-[0.5px]">Due Date</span><span class="date-val text-[13px] font-semibold text-[#334155] due">June 15, 2024</span></div>
          <div class="flex flex-col gap-[3px]"><span class="text-xs font-semibold text-[#94a3b8] uppercase tracking-[0.5px]">Status</span><span class="inline-block bg-[#fef3c7] text-[#92400e] text-[11px] font-bold py-[3px] px-2.5 rounded-[20px]">Unpaid</span></div>
        </div>
      </div>
      <div class="grid grid-cols-2 gap-6 py-6 px-8 border-b border-b-[#f1f5f9]">
        <div class="party">
          <div class="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">From</div>
          <div class="text-[15px] font-bold text-[#1e293b] mb-1">Acme Studio</div>
          <div class="text-xs text-[#64748b] leading-[1.7]">[email protected]</div>
          <div class="text-xs text-[#64748b] leading-[1.7]">San Francisco, CA 94105</div>
          <div class="text-xs text-[#64748b] leading-[1.7]">Tax ID: US-98765432</div>
        </div>
        <div class="party">
          <div class="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">Bill To</div>
          <div class="text-[15px] font-bold text-[#1e293b] mb-1">TechFlow Inc.</div>
          <div class="text-xs text-[#64748b] leading-[1.7]">[email protected]</div>
          <div class="text-xs text-[#64748b] leading-[1.7]">Austin, TX 78701</div>
          <div class="text-xs text-[#64748b] leading-[1.7]">PO: PO-2024-0041</div>
        </div>
      </div>
      <table class="items-table w-full [border-collapse:collapse] py-0 px-8 table p-0">
        <thead>
          <tr>
            <th class="text-left text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pl-8">Description</th>
            <th class="text-right text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pr-8">Qty</th>
            <th class="text-right text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pr-8">Rate</th>
            <th class="text-right text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pr-8">Amount</th>
          </tr>
        </thead>
        <tbody>
          <tr class="item-row border-b border-b-[#f8fafc]">
            <td>
              <div class="text-sm font-semibold text-[#1e293b]">UI Design — Dashboard Screens</div>
              <div class="text-xs text-[#94a3b8] mt-0.5">Figma, 12 screens, 2 rounds of revision</div>
            </td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">1</td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">$3,200.00</td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums] bold">$3,200.00</td>
          </tr>
          <tr class="item-row border-b border-b-[#f8fafc]">
            <td>
              <div class="text-sm font-semibold text-[#1e293b]">Frontend Development</div>
              <div class="text-xs text-[#94a3b8] mt-0.5">React + Tailwind, component library integration</div>
            </td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">40 hr</td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">$95.00</td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums] bold">$3,800.00</td>
          </tr>
          <tr class="item-row border-b border-b-[#f8fafc]">
            <td>
              <div class="text-sm font-semibold text-[#1e293b]">Monthly Maintenance</div>
              <div class="text-xs text-[#94a3b8] mt-0.5">May 2024 — bug fixes, updates, monitoring</div>
            </td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">1</td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">$450.00</td>
            <td class="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums] bold">$450.00</td>
          </tr>
        </tbody>
      </table>
      <div class="flex justify-end py-5 px-8 border-b border-b-[#f1f5f9]">
        <div class="w-60 flex flex-col gap-2">
          <div class="flex justify-between text-[13px] text-[#64748b]"><span>Subtotal</span><span>$7,450.00</span></div>
          <div class="flex justify-between text-[13px] text-[#64748b]"><span>Tax (8%)</span><span>$596.00</span></div>
          <div class="flex justify-between text-[13px] text-[#64748b]"><span>Discount</span><span class="text-[#16a34a]">&#x2212;$200.00</span></div>
          <div class="flex justify-between text-[17px] font-extrabold text-[#1e293b] pt-2.5 border-t-2 border-t-[#e2e8f0] mt-1"><span>Total Due</span><span>$7,846.00</span></div>
        </div>
      </div>
      <div class="grid grid-cols-2 gap-6 py-5 px-8 border-b border-b-[#f1f5f9] bg-[#fafafa]">
        <div class="note">
          <div class="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">Payment Terms</div>
          <div class="text-xs text-[#64748b] leading-[1.6]">Payment due within 14 days. Bank transfer or PayPal accepted. Late payments may incur a 2% monthly fee.</div>
        </div>
        <div class="bank">
          <div class="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">Bank Details</div>
          <div class="text-xs text-[#64748b] leading-[1.6]">Bank: Chase &nbsp;&middot;&nbsp; Account: 0042-8812-09 &nbsp;&middot;&nbsp; Routing: 021000021</div>
        </div>
      </div>
      <div class="inv-bottom flex justify-between items-center py-4 px-8">
        <span>Thank you for your business!</span>
        <button class="flex items-center gap-1.5 bg-[#1e293b] text-[#fff] border-0 py-[9px] px-4 rounded-lg text-[13px] font-semibold cursor-pointer [transition:background_0.15s] hover:bg-[#0f172a] hidden" onclick="window.print()">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
          Print / Save PDF
        </button>
      </div>
    </div>
  </div>
</body>
</html>
React
import React from 'react';

// CSS — optionally move to InvoicePreview.module.css
const css = `
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #f1f5f9; min-height: 100vh; padding: 32px 20px; display: flex; justify-content: center; }
.page { width: 100%; }
.invoice { background: #fff; border-radius: 16px; max-width: 720px; margin: 0 auto; box-shadow: 0 4px 24px rgba(0,0,0,0.08); overflow: hidden; }
.inv-head { background: #f8fafc; padding: 28px 32px; border-bottom: 1px solid #e2e8f0; }
.inv-brand { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; }
.logo { display: flex; align-items: center; gap: 10px; }
.brand-name { font-size: 18px; font-weight: 800; color: #1e293b; }
.inv-meta { text-align: right; }
.inv-badge { font-size: 10px; font-weight: 800; letter-spacing: 2px; color: #6366f1; background: rgba(99,102,241,0.08); border-radius: 6px; padding: 3px 8px; display: inline-block; margin-bottom: 4px; }
.inv-num { font-size: 14px; font-weight: 600; color: #475569; }
.inv-dates { display: flex; gap: 24px; flex-wrap: wrap; }
.date-row { display: flex; flex-direction: column; gap: 3px; }
.date-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
.date-val { font-size: 13px; font-weight: 600; color: #334155; }
.date-val.due { color: #ef4444; }
.status-chip { display: inline-block; background: #fef3c7; color: #92400e; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 20px; }
.parties { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px 32px; border-bottom: 1px solid #f1f5f9; }
.party-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.party-name { font-size: 15px; font-weight: 700; color: #1e293b; margin-bottom: 4px; }
.party-detail { font-size: 12px; color: #64748b; line-height: 1.7; }
.items-table { width: 100%; border-collapse: collapse; padding: 0 32px; display: table; }
.items-table { padding: 0; }
.items-table th, .items-table td { padding: 12px 16px; }
.items-table thead tr { border-bottom: 2px solid #f1f5f9; }
.th-desc { text-align: left; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-left: 32px; }
.th-num { text-align: right; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-right: 32px; }
.item-row { border-bottom: 1px solid #f8fafc; }
.item-row td:first-child { padding-left: 32px; }
.item-row td:last-child { padding-right: 32px; }
.item-title { font-size: 14px; font-weight: 600; color: #1e293b; }
.item-sub { font-size: 12px; color: #94a3b8; margin-top: 2px; }
.num { text-align: right; font-size: 14px; color: #475569; font-variant-numeric: tabular-nums; }
.num.bold { font-weight: 700; color: #1e293b; }
.totals-wrap { display: flex; justify-content: flex-end; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; }
.totals { width: 240px; display: flex; flex-direction: column; gap: 8px; }
.tot-row { display: flex; justify-content: space-between; font-size: 13px; color: #64748b; }
.discount { color: #16a34a; }
.tot-final { display: flex; justify-content: space-between; font-size: 17px; font-weight: 800; color: #1e293b; padding-top: 10px; border-top: 2px solid #e2e8f0; margin-top: 4px; }
.inv-footer { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; background: #fafafa; }
.note-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.note-text { font-size: 12px; color: #64748b; line-height: 1.6; }
.inv-bottom { display: flex; justify-content: space-between; align-items: center; padding: 16px 32px; }
.inv-bottom span { font-size: 13px; color: #94a3b8; font-style: italic; }
.print-btn { display: flex; align-items: center; gap: 6px; background: #1e293b; color: #fff; border: none; padding: 9px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: background 0.15s; }
.print-btn:hover { background: #0f172a; }
@media print { body { background: #fff; padding: 0; } .invoice { box-shadow: none; border-radius: 0; } .print-btn { display: none; } }
`;

export default function InvoicePreview() {
  return (
    <>
      <style>{css}</style>
      <div className="page">
        <div className="invoice">
          <div className="inv-head">
            <div className="inv-brand">
              <div className="logo">
                <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="7" fill="#6366f1"/><path d="M8 20L14 8l6 12" stroke="white" strokeWidth="2.5" strokeLinecap="round" stroke-linejoin="round"/><path d="M10 16h8" stroke="white" strokeWidth="2.5" strokeLinecap="round"/></svg>
                <span className="brand-name">Acme Studio</span>
              </div>
              <div className="inv-meta">
                <div className="inv-badge">INVOICE</div>
                <div className="inv-num">#INV-2024-089</div>
              </div>
            </div>
            <div className="inv-dates">
              <div className="date-row"><span className="date-label">Issue Date</span><span className="date-val">June 1, 2024</span></div>
              <div className="date-row"><span className="date-label">Due Date</span><span className="date-val due">June 15, 2024</span></div>
              <div className="date-row"><span className="date-label">Status</span><span className="status-chip">Unpaid</span></div>
            </div>
          </div>
          <div className="parties">
            <div className="party">
              <div className="party-label">From</div>
              <div className="party-name">Acme Studio</div>
              <div className="party-detail">[email protected]</div>
              <div className="party-detail">San Francisco, CA 94105</div>
              <div className="party-detail">Tax ID: US-98765432</div>
            </div>
            <div className="party">
              <div className="party-label">Bill To</div>
              <div className="party-name">TechFlow Inc.</div>
              <div className="party-detail">[email protected]</div>
              <div className="party-detail">Austin, TX 78701</div>
              <div className="party-detail">PO: PO-2024-0041</div>
            </div>
          </div>
          <table className="items-table">
            <thead>
              <tr>
                <th className="th-desc">Description</th>
                <th className="th-num">Qty</th>
                <th className="th-num">Rate</th>
                <th className="th-num">Amount</th>
              </tr>
            </thead>
            <tbody>
              <tr className="item-row">
                <td>
                  <div className="item-title">UI Design — Dashboard Screens</div>
                  <div className="item-sub">Figma, 12 screens, 2 rounds of revision</div>
                </td>
                <td className="num">1</td>
                <td className="num">$3,200.00</td>
                <td className="num bold">$3,200.00</td>
              </tr>
              <tr className="item-row">
                <td>
                  <div className="item-title">Frontend Development</div>
                  <div className="item-sub">React + Tailwind, component library integration</div>
                </td>
                <td className="num">40 hr</td>
                <td className="num">$95.00</td>
                <td className="num bold">$3,800.00</td>
              </tr>
              <tr className="item-row">
                <td>
                  <div className="item-title">Monthly Maintenance</div>
                  <div className="item-sub">May 2024 — bug fixes, updates, monitoring</div>
                </td>
                <td className="num">1</td>
                <td className="num">$450.00</td>
                <td className="num bold">$450.00</td>
              </tr>
            </tbody>
          </table>
          <div className="totals-wrap">
            <div className="totals">
              <div className="tot-row"><span>Subtotal</span><span>$7,450.00</span></div>
              <div className="tot-row"><span>Tax (8%)</span><span>$596.00</span></div>
              <div className="tot-row"><span>Discount</span><span className="discount">&#x2212;$200.00</span></div>
              <div className="tot-final"><span>Total Due</span><span>$7,846.00</span></div>
            </div>
          </div>
          <div className="inv-footer">
            <div className="note">
              <div className="note-label">Payment Terms</div>
              <div className="note-text">Payment due within 14 days. Bank transfer or PayPal accepted. Late payments may incur a 2% monthly fee.</div>
            </div>
            <div className="bank">
              <div className="note-label">Bank Details</div>
              <div className="note-text">Bank: Chase &nbsp;&middot;&nbsp; Account: 0042-8812-09 &nbsp;&middot;&nbsp; Routing: 021000021</div>
            </div>
          </div>
          <div className="inv-bottom">
            <span>Thank you for your business!</span>
            <button className="print-btn" onClick={(event) => { window.print() }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
              Print / Save PDF
            </button>
          </div>
        </div>
      </div>
    </>
  );
}
React + Tailwind
import React from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+

export default function InvoicePreview() {
  return (
    <>
      <style>{`
* {
  box-sizing: border-box; margin: 0; padding: 0;
}

body {
  font-family: system-ui, sans-serif; background: #f1f5f9; min-height: 100vh; padding: 32px 20px; display: flex; justify-content: center;
}

.date-val.due {
  color: #ef4444;
}

.items-table th, .items-table td {
  padding: 12px 16px;
}

.items-table thead tr {
  border-bottom: 2px solid #f1f5f9;
}

.item-row td:first-child {
  padding-left: 32px;
}

.item-row td:last-child {
  padding-right: 32px;
}

.num.bold {
  font-weight: 700; color: #1e293b;
}

.inv-bottom span {
  font-size: 13px; color: #94a3b8; font-style: italic;
}

body {
  background: #fff; padding: 0;
}
      `}</style>
      <div className="w-full">
        <div className="bg-[#fff] rounded-2xl max-w-[720px] my-0 mx-auto shadow-[0_4px_24px_rgba(0,0,0,0.08)] overflow-hidden shadow-none rounded-none">
          <div className="bg-[#f8fafc] py-7 px-8 border-b border-b-[#e2e8f0]">
            <div className="flex justify-between items-start mb-[18px]">
              <div className="flex items-center gap-2.5">
                <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="7" fill="#6366f1"/><path d="M8 20L14 8l6 12" stroke="white" strokeWidth="2.5" strokeLinecap="round" stroke-linejoin="round"/><path d="M10 16h8" stroke="white" strokeWidth="2.5" strokeLinecap="round"/></svg>
                <span className="text-lg font-extrabold text-[#1e293b]">Acme Studio</span>
              </div>
              <div className="text-right">
                <div className="text-xs font-extrabold tracking-[2px] text-[#6366f1] bg-[rgba(99,102,241,0.08)] rounded-md py-[3px] px-2 inline-block mb-1">INVOICE</div>
                <div className="text-sm font-semibold text-[#475569]">#INV-2024-089</div>
              </div>
            </div>
            <div className="flex gap-6 flex-wrap">
              <div className="flex flex-col gap-[3px]"><span className="text-xs font-semibold text-[#94a3b8] uppercase tracking-[0.5px]">Issue Date</span><span className="date-val text-[13px] font-semibold text-[#334155]">June 1, 2024</span></div>
              <div className="flex flex-col gap-[3px]"><span className="text-xs font-semibold text-[#94a3b8] uppercase tracking-[0.5px]">Due Date</span><span className="date-val text-[13px] font-semibold text-[#334155] due">June 15, 2024</span></div>
              <div className="flex flex-col gap-[3px]"><span className="text-xs font-semibold text-[#94a3b8] uppercase tracking-[0.5px]">Status</span><span className="inline-block bg-[#fef3c7] text-[#92400e] text-[11px] font-bold py-[3px] px-2.5 rounded-[20px]">Unpaid</span></div>
            </div>
          </div>
          <div className="grid grid-cols-2 gap-6 py-6 px-8 border-b border-b-[#f1f5f9]">
            <div className="party">
              <div className="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">From</div>
              <div className="text-[15px] font-bold text-[#1e293b] mb-1">Acme Studio</div>
              <div className="text-xs text-[#64748b] leading-[1.7]">[email protected]</div>
              <div className="text-xs text-[#64748b] leading-[1.7]">San Francisco, CA 94105</div>
              <div className="text-xs text-[#64748b] leading-[1.7]">Tax ID: US-98765432</div>
            </div>
            <div className="party">
              <div className="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">Bill To</div>
              <div className="text-[15px] font-bold text-[#1e293b] mb-1">TechFlow Inc.</div>
              <div className="text-xs text-[#64748b] leading-[1.7]">[email protected]</div>
              <div className="text-xs text-[#64748b] leading-[1.7]">Austin, TX 78701</div>
              <div className="text-xs text-[#64748b] leading-[1.7]">PO: PO-2024-0041</div>
            </div>
          </div>
          <table className="items-table w-full [border-collapse:collapse] py-0 px-8 table p-0">
            <thead>
              <tr>
                <th className="text-left text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pl-8">Description</th>
                <th className="text-right text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pr-8">Qty</th>
                <th className="text-right text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pr-8">Rate</th>
                <th className="text-right text-[11px] font-bold text-[#94a3b8] uppercase tracking-[0.5px] pr-8">Amount</th>
              </tr>
            </thead>
            <tbody>
              <tr className="item-row border-b border-b-[#f8fafc]">
                <td>
                  <div className="text-sm font-semibold text-[#1e293b]">UI Design — Dashboard Screens</div>
                  <div className="text-xs text-[#94a3b8] mt-0.5">Figma, 12 screens, 2 rounds of revision</div>
                </td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">1</td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">$3,200.00</td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums] bold">$3,200.00</td>
              </tr>
              <tr className="item-row border-b border-b-[#f8fafc]">
                <td>
                  <div className="text-sm font-semibold text-[#1e293b]">Frontend Development</div>
                  <div className="text-xs text-[#94a3b8] mt-0.5">React + Tailwind, component library integration</div>
                </td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">40 hr</td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">$95.00</td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums] bold">$3,800.00</td>
              </tr>
              <tr className="item-row border-b border-b-[#f8fafc]">
                <td>
                  <div className="text-sm font-semibold text-[#1e293b]">Monthly Maintenance</div>
                  <div className="text-xs text-[#94a3b8] mt-0.5">May 2024 — bug fixes, updates, monitoring</div>
                </td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">1</td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums]">$450.00</td>
                <td className="num text-right text-sm text-[#475569] [font-variant-numeric:tabular-nums] bold">$450.00</td>
              </tr>
            </tbody>
          </table>
          <div className="flex justify-end py-5 px-8 border-b border-b-[#f1f5f9]">
            <div className="w-60 flex flex-col gap-2">
              <div className="flex justify-between text-[13px] text-[#64748b]"><span>Subtotal</span><span>$7,450.00</span></div>
              <div className="flex justify-between text-[13px] text-[#64748b]"><span>Tax (8%)</span><span>$596.00</span></div>
              <div className="flex justify-between text-[13px] text-[#64748b]"><span>Discount</span><span className="text-[#16a34a]">&#x2212;$200.00</span></div>
              <div className="flex justify-between text-[17px] font-extrabold text-[#1e293b] pt-2.5 border-t-2 border-t-[#e2e8f0] mt-1"><span>Total Due</span><span>$7,846.00</span></div>
            </div>
          </div>
          <div className="grid grid-cols-2 gap-6 py-5 px-8 border-b border-b-[#f1f5f9] bg-[#fafafa]">
            <div className="note">
              <div className="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">Payment Terms</div>
              <div className="text-xs text-[#64748b] leading-[1.6]">Payment due within 14 days. Bank transfer or PayPal accepted. Late payments may incur a 2% monthly fee.</div>
            </div>
            <div className="bank">
              <div className="text-xs font-bold text-[#94a3b8] uppercase tracking-[0.5px] mb-1.5">Bank Details</div>
              <div className="text-xs text-[#64748b] leading-[1.6]">Bank: Chase &nbsp;&middot;&nbsp; Account: 0042-8812-09 &nbsp;&middot;&nbsp; Routing: 021000021</div>
            </div>
          </div>
          <div className="inv-bottom flex justify-between items-center py-4 px-8">
            <span>Thank you for your business!</span>
            <button className="flex items-center gap-1.5 bg-[#1e293b] text-[#fff] border-0 py-[9px] px-4 rounded-lg text-[13px] font-semibold cursor-pointer [transition:background_0.15s] hover:bg-[#0f172a] hidden" onClick={(event) => { window.print() }}>
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
              Print / Save PDF
            </button>
          </div>
        </div>
      </div>
    </>
  );
}
Vue
<template>
  <div class="page">
    <div class="invoice">
      <div class="inv-head">
        <div class="inv-brand">
          <div class="logo">
            <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="7" fill="#6366f1"/><path d="M8 20L14 8l6 12" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 16h8" stroke="white" stroke-width="2.5" stroke-linecap="round"/></svg>
            <span class="brand-name">Acme Studio</span>
          </div>
          <div class="inv-meta">
            <div class="inv-badge">INVOICE</div>
            <div class="inv-num">#INV-2024-089</div>
          </div>
        </div>
        <div class="inv-dates">
          <div class="date-row"><span class="date-label">Issue Date</span><span class="date-val">June 1, 2024</span></div>
          <div class="date-row"><span class="date-label">Due Date</span><span class="date-val due">June 15, 2024</span></div>
          <div class="date-row"><span class="date-label">Status</span><span class="status-chip">Unpaid</span></div>
        </div>
      </div>
      <div class="parties">
        <div class="party">
          <div class="party-label">From</div>
          <div class="party-name">Acme Studio</div>
          <div class="party-detail">[email protected]</div>
          <div class="party-detail">San Francisco, CA 94105</div>
          <div class="party-detail">Tax ID: US-98765432</div>
        </div>
        <div class="party">
          <div class="party-label">Bill To</div>
          <div class="party-name">TechFlow Inc.</div>
          <div class="party-detail">[email protected]</div>
          <div class="party-detail">Austin, TX 78701</div>
          <div class="party-detail">PO: PO-2024-0041</div>
        </div>
      </div>
      <table class="items-table">
        <thead>
          <tr>
            <th class="th-desc">Description</th>
            <th class="th-num">Qty</th>
            <th class="th-num">Rate</th>
            <th class="th-num">Amount</th>
          </tr>
        </thead>
        <tbody>
          <tr class="item-row">
            <td>
              <div class="item-title">UI Design — Dashboard Screens</div>
              <div class="item-sub">Figma, 12 screens, 2 rounds of revision</div>
            </td>
            <td class="num">1</td>
            <td class="num">$3,200.00</td>
            <td class="num bold">$3,200.00</td>
          </tr>
          <tr class="item-row">
            <td>
              <div class="item-title">Frontend Development</div>
              <div class="item-sub">React + Tailwind, component library integration</div>
            </td>
            <td class="num">40 hr</td>
            <td class="num">$95.00</td>
            <td class="num bold">$3,800.00</td>
          </tr>
          <tr class="item-row">
            <td>
              <div class="item-title">Monthly Maintenance</div>
              <div class="item-sub">May 2024 — bug fixes, updates, monitoring</div>
            </td>
            <td class="num">1</td>
            <td class="num">$450.00</td>
            <td class="num bold">$450.00</td>
          </tr>
        </tbody>
      </table>
      <div class="totals-wrap">
        <div class="totals">
          <div class="tot-row"><span>Subtotal</span><span>$7,450.00</span></div>
          <div class="tot-row"><span>Tax (8%)</span><span>$596.00</span></div>
          <div class="tot-row"><span>Discount</span><span class="discount">&#x2212;$200.00</span></div>
          <div class="tot-final"><span>Total Due</span><span>$7,846.00</span></div>
        </div>
      </div>
      <div class="inv-footer">
        <div class="note">
          <div class="note-label">Payment Terms</div>
          <div class="note-text">Payment due within 14 days. Bank transfer or PayPal accepted. Late payments may incur a 2% monthly fee.</div>
        </div>
        <div class="bank">
          <div class="note-label">Bank Details</div>
          <div class="note-text">Bank: Chase &nbsp;&middot;&nbsp; Account: 0042-8812-09 &nbsp;&middot;&nbsp; Routing: 021000021</div>
        </div>
      </div>
      <div class="inv-bottom">
        <span>Thank you for your business!</span>
        <button class="print-btn" @click="window.print()">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
          Print / Save PDF
        </button>
      </div>
    </div>
  </div>
</template>

<script setup></script>

<style scoped>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #f1f5f9; min-height: 100vh; padding: 32px 20px; display: flex; justify-content: center; }
.page { width: 100%; }
.invoice { background: #fff; border-radius: 16px; max-width: 720px; margin: 0 auto; box-shadow: 0 4px 24px rgba(0,0,0,0.08); overflow: hidden; }
.inv-head { background: #f8fafc; padding: 28px 32px; border-bottom: 1px solid #e2e8f0; }
.inv-brand { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; }
.logo { display: flex; align-items: center; gap: 10px; }
.brand-name { font-size: 18px; font-weight: 800; color: #1e293b; }
.inv-meta { text-align: right; }
.inv-badge { font-size: 10px; font-weight: 800; letter-spacing: 2px; color: #6366f1; background: rgba(99,102,241,0.08); border-radius: 6px; padding: 3px 8px; display: inline-block; margin-bottom: 4px; }
.inv-num { font-size: 14px; font-weight: 600; color: #475569; }
.inv-dates { display: flex; gap: 24px; flex-wrap: wrap; }
.date-row { display: flex; flex-direction: column; gap: 3px; }
.date-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
.date-val { font-size: 13px; font-weight: 600; color: #334155; }
.date-val.due { color: #ef4444; }
.status-chip { display: inline-block; background: #fef3c7; color: #92400e; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 20px; }
.parties { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px 32px; border-bottom: 1px solid #f1f5f9; }
.party-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.party-name { font-size: 15px; font-weight: 700; color: #1e293b; margin-bottom: 4px; }
.party-detail { font-size: 12px; color: #64748b; line-height: 1.7; }
.items-table { width: 100%; border-collapse: collapse; padding: 0 32px; display: table; }
.items-table { padding: 0; }
.items-table th, .items-table td { padding: 12px 16px; }
.items-table thead tr { border-bottom: 2px solid #f1f5f9; }
.th-desc { text-align: left; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-left: 32px; }
.th-num { text-align: right; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-right: 32px; }
.item-row { border-bottom: 1px solid #f8fafc; }
.item-row td:first-child { padding-left: 32px; }
.item-row td:last-child { padding-right: 32px; }
.item-title { font-size: 14px; font-weight: 600; color: #1e293b; }
.item-sub { font-size: 12px; color: #94a3b8; margin-top: 2px; }
.num { text-align: right; font-size: 14px; color: #475569; font-variant-numeric: tabular-nums; }
.num.bold { font-weight: 700; color: #1e293b; }
.totals-wrap { display: flex; justify-content: flex-end; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; }
.totals { width: 240px; display: flex; flex-direction: column; gap: 8px; }
.tot-row { display: flex; justify-content: space-between; font-size: 13px; color: #64748b; }
.discount { color: #16a34a; }
.tot-final { display: flex; justify-content: space-between; font-size: 17px; font-weight: 800; color: #1e293b; padding-top: 10px; border-top: 2px solid #e2e8f0; margin-top: 4px; }
.inv-footer { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; background: #fafafa; }
.note-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
.note-text { font-size: 12px; color: #64748b; line-height: 1.6; }
.inv-bottom { display: flex; justify-content: space-between; align-items: center; padding: 16px 32px; }
.inv-bottom span { font-size: 13px; color: #94a3b8; font-style: italic; }
.print-btn { display: flex; align-items: center; gap: 6px; background: #1e293b; color: #fff; border: none; padding: 9px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: background 0.15s; }
.print-btn:hover { background: #0f172a; }
@media print { body { background: #fff; padding: 0; } .invoice { box-shadow: none; border-radius: 0; } .print-btn { display: none; } }
</style>
Angular
// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-invoice-preview',
  standalone: true,
  imports: [CommonModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="page">
      <div class="invoice">
        <div class="inv-head">
          <div class="inv-brand">
            <div class="logo">
              <svg width="28" height="28" viewBox="0 0 28 28" fill="none"><rect width="28" height="28" rx="7" fill="#6366f1"/><path d="M8 20L14 8l6 12" stroke="white" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M10 16h8" stroke="white" stroke-width="2.5" stroke-linecap="round"/></svg>
              <span class="brand-name">Acme Studio</span>
            </div>
            <div class="inv-meta">
              <div class="inv-badge">INVOICE</div>
              <div class="inv-num">#INV-2024-089</div>
            </div>
          </div>
          <div class="inv-dates">
            <div class="date-row"><span class="date-label">Issue Date</span><span class="date-val">June 1, 2024</span></div>
            <div class="date-row"><span class="date-label">Due Date</span><span class="date-val due">June 15, 2024</span></div>
            <div class="date-row"><span class="date-label">Status</span><span class="status-chip">Unpaid</span></div>
          </div>
        </div>
        <div class="parties">
          <div class="party">
            <div class="party-label">From</div>
            <div class="party-name">Acme Studio</div>
            <div class="party-detail">[email protected]</div>
            <div class="party-detail">San Francisco, CA 94105</div>
            <div class="party-detail">Tax ID: US-98765432</div>
          </div>
          <div class="party">
            <div class="party-label">Bill To</div>
            <div class="party-name">TechFlow Inc.</div>
            <div class="party-detail">[email protected]</div>
            <div class="party-detail">Austin, TX 78701</div>
            <div class="party-detail">PO: PO-2024-0041</div>
          </div>
        </div>
        <table class="items-table">
          <thead>
            <tr>
              <th class="th-desc">Description</th>
              <th class="th-num">Qty</th>
              <th class="th-num">Rate</th>
              <th class="th-num">Amount</th>
            </tr>
          </thead>
          <tbody>
            <tr class="item-row">
              <td>
                <div class="item-title">UI Design — Dashboard Screens</div>
                <div class="item-sub">Figma, 12 screens, 2 rounds of revision</div>
              </td>
              <td class="num">1</td>
              <td class="num">$3,200.00</td>
              <td class="num bold">$3,200.00</td>
            </tr>
            <tr class="item-row">
              <td>
                <div class="item-title">Frontend Development</div>
                <div class="item-sub">React + Tailwind, component library integration</div>
              </td>
              <td class="num">40 hr</td>
              <td class="num">$95.00</td>
              <td class="num bold">$3,800.00</td>
            </tr>
            <tr class="item-row">
              <td>
                <div class="item-title">Monthly Maintenance</div>
                <div class="item-sub">May 2024 — bug fixes, updates, monitoring</div>
              </td>
              <td class="num">1</td>
              <td class="num">$450.00</td>
              <td class="num bold">$450.00</td>
            </tr>
          </tbody>
        </table>
        <div class="totals-wrap">
          <div class="totals">
            <div class="tot-row"><span>Subtotal</span><span>$7,450.00</span></div>
            <div class="tot-row"><span>Tax (8%)</span><span>$596.00</span></div>
            <div class="tot-row"><span>Discount</span><span class="discount">&#x2212;$200.00</span></div>
            <div class="tot-final"><span>Total Due</span><span>$7,846.00</span></div>
          </div>
        </div>
        <div class="inv-footer">
          <div class="note">
            <div class="note-label">Payment Terms</div>
            <div class="note-text">Payment due within 14 days. Bank transfer or PayPal accepted. Late payments may incur a 2% monthly fee.</div>
          </div>
          <div class="bank">
            <div class="note-label">Bank Details</div>
            <div class="note-text">Bank: Chase &nbsp;&middot;&nbsp; Account: 0042-8812-09 &nbsp;&middot;&nbsp; Routing: 021000021</div>
          </div>
        </div>
        <div class="inv-bottom">
          <span>Thank you for your business!</span>
          <button class="print-btn" (click)="window.print()">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
            Print / Save PDF
          </button>
        </div>
      </div>
    </div>
  `,
  styles: [`
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, sans-serif; background: #f1f5f9; min-height: 100vh; padding: 32px 20px; display: flex; justify-content: center; }
    .page { width: 100%; }
    .invoice { background: #fff; border-radius: 16px; max-width: 720px; margin: 0 auto; box-shadow: 0 4px 24px rgba(0,0,0,0.08); overflow: hidden; }
    .inv-head { background: #f8fafc; padding: 28px 32px; border-bottom: 1px solid #e2e8f0; }
    .inv-brand { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; }
    .logo { display: flex; align-items: center; gap: 10px; }
    .brand-name { font-size: 18px; font-weight: 800; color: #1e293b; }
    .inv-meta { text-align: right; }
    .inv-badge { font-size: 10px; font-weight: 800; letter-spacing: 2px; color: #6366f1; background: rgba(99,102,241,0.08); border-radius: 6px; padding: 3px 8px; display: inline-block; margin-bottom: 4px; }
    .inv-num { font-size: 14px; font-weight: 600; color: #475569; }
    .inv-dates { display: flex; gap: 24px; flex-wrap: wrap; }
    .date-row { display: flex; flex-direction: column; gap: 3px; }
    .date-label { font-size: 10px; font-weight: 600; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; }
    .date-val { font-size: 13px; font-weight: 600; color: #334155; }
    .date-val.due { color: #ef4444; }
    .status-chip { display: inline-block; background: #fef3c7; color: #92400e; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 20px; }
    .parties { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 24px 32px; border-bottom: 1px solid #f1f5f9; }
    .party-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
    .party-name { font-size: 15px; font-weight: 700; color: #1e293b; margin-bottom: 4px; }
    .party-detail { font-size: 12px; color: #64748b; line-height: 1.7; }
    .items-table { width: 100%; border-collapse: collapse; padding: 0 32px; display: table; }
    .items-table { padding: 0; }
    .items-table th, .items-table td { padding: 12px 16px; }
    .items-table thead tr { border-bottom: 2px solid #f1f5f9; }
    .th-desc { text-align: left; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-left: 32px; }
    .th-num { text-align: right; font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; padding-right: 32px; }
    .item-row { border-bottom: 1px solid #f8fafc; }
    .item-row td:first-child { padding-left: 32px; }
    .item-row td:last-child { padding-right: 32px; }
    .item-title { font-size: 14px; font-weight: 600; color: #1e293b; }
    .item-sub { font-size: 12px; color: #94a3b8; margin-top: 2px; }
    .num { text-align: right; font-size: 14px; color: #475569; font-variant-numeric: tabular-nums; }
    .num.bold { font-weight: 700; color: #1e293b; }
    .totals-wrap { display: flex; justify-content: flex-end; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; }
    .totals { width: 240px; display: flex; flex-direction: column; gap: 8px; }
    .tot-row { display: flex; justify-content: space-between; font-size: 13px; color: #64748b; }
    .discount { color: #16a34a; }
    .tot-final { display: flex; justify-content: space-between; font-size: 17px; font-weight: 800; color: #1e293b; padding-top: 10px; border-top: 2px solid #e2e8f0; margin-top: 4px; }
    .inv-footer { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; padding: 20px 32px; border-bottom: 1px solid #f1f5f9; background: #fafafa; }
    .note-label { font-size: 10px; font-weight: 700; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 6px; }
    .note-text { font-size: 12px; color: #64748b; line-height: 1.6; }
    .inv-bottom { display: flex; justify-content: space-between; align-items: center; padding: 16px 32px; }
    .inv-bottom span { font-size: 13px; color: #94a3b8; font-style: italic; }
    .print-btn { display: flex; align-items: center; gap: 6px; background: #1e293b; color: #fff; border: none; padding: 9px 16px; border-radius: 8px; font-size: 13px; font-weight: 600; cursor: pointer; transition: background 0.15s; }
    .print-btn:hover { background: #0f172a; }
    @media print { body { background: #fff; padding: 0; } .invoice { box-shadow: none; border-radius: 0; } .print-btn { display: none; } }
  `]
})
export class InvoicePreviewComponent {}