Why This Project?
Chapters 01 and 02 taught you how to save data to the browser. Chapter 03 teaches you how to transform data — take raw form inputs, apply math, and render a fully formatted document in real time. This is the pattern behind every report builder, dashboard, and document editor on the web.
The invoice generator has no backend, no database, no login. It runs entirely in the browser and produces a print-ready PDF via the browser’s native print dialog. That’s a professional deliverable built with nothing but HTML, CSS, and JavaScript.
Form + Live Render
Every oninput event on any field immediately re-renders the invoice preview. The form and the output are always in sync.
Template Literals
The entire invoice HTML is assembled as a JavaScript template literal — a string with embedded expressions. This is how real templating engines work under the hood.
Arithmetic in JS
Qty × Rate, subtotal, discount, tax, total due — all calculated live from input values. parseFloat, toFixed, and reducing an array of line items.
window.print()
The PDF export works by opening a new browser window with only the invoice HTML, applying print-optimized CSS, then calling window.print(). No libraries needed.
The AI Angle for This Chapter
In Chapter 02, AI helped you build a system of components. Here, the challenge is precision — the math has to be exactly right, the layout has to survive edge cases (no tax, no discount, one line item, twenty line items), and the print output has to look professional.
AI is excellent at arithmetic logic — but you need to specify every edge case. What happens when qty is 0? When the rate field is empty? When discount is greater than 100%? Writing these constraints into your prompts is what separates a working tool from a fragile demo.
Before prompting the math section, define your edge cases:
- What should the total show if there are no line items?
- Should discount apply before or after tax? (It matters.)
- What happens if the user types letters into the rate field?
- How many decimal places should money values display?
Before You Build — Map the Data Flow
This app has a clear one-way data flow: inputs → calculation → render. Trace it before you build:
- List every piece of information on the invoice. Group them: sender info, client info, line items, totals, metadata.
- Which fields are typed once and stay fixed? Which ones recalculate every time something changes?
- Walk through the math: if you have 3 line items, a 10% discount, and 8% tax, what is the order of operations to get the total?
- What’s the difference between the invoice layout in the browser (dark background, form on the left) and the printed version (white, no form)? How would you handle that with CSS?
- Where does localStorage fit in this app? What specifically should it save and when?
If you can answer these five questions, your prompts will be surgical rather than exploratory — and your app will work correctly on the first real-world use.
Build It
Scaffold the split-pane layout
This app uses a two-column grid: the form on the left, the live invoice preview on the right. The preview is a white “paper” card on a dark background — a visual metaphor that makes the print-ready output obvious at a glance.
Build a two-column layout. Left column: a dark scrollable form pane.
Right column: a dark background with a white centered "invoice sheet"
div that looks like a piece of paper. Nav bar at top with links.
No frameworks. Vanilla HTML/CSS/JS only. Color palette: #111 background,
#e00 red accents, white paper for the invoice sheet.
Build the form fields and wire oninput
Add all the form fields grouped by section: From, Bill To, Invoice Details, Line Items, Tax & Notes. Every input field should call a render() function on each keystroke. Don’t build the render function yet — just wire the event and confirm it fires.
Add form fields in grouped sections: sender name/email/address,
client name/email/address, invoice number, issue date, due date,
currency selector, tax rate %, discount %, and notes textarea.
Every input should call render() on oninput/onchange.
Also add an "+ Add Line" button that appends a new row with
description, qty, rate inputs and a delete button.
Build the render() function
This is the core of the app. The render function reads all current form values, calculates totals, and rebuilds the entire invoice sheet HTML using a template literal. It runs on every input change.
Write a render() function that:
1. Reads all form field values
2. Calculates: subtotal (sum of qty*rate), discount amount,
taxable amount, tax amount, total due
3. Builds the invoice HTML as a template literal string
4. Sets document.getElementById('invoice-sheet').innerHTML
to that string
Money values should always show 2 decimal places.
If a number field is empty, treat it as 0.
Add PDF export via window.print()
The print export works by opening a new window containing only the invoice HTML with its own embedded print CSS, then triggering the browser’s print dialog. The user saves as PDF from there. No libraries, no server.
Write a printInvoice() function that:
1. Clones the invoice sheet HTML
2. Opens a new window with window.open()
3. Writes a complete HTML document to that window including
all necessary CSS (white background, proper typography,
no dark UI elements)
4. Calls window.print() after a short timeout to ensure
the styles have loaded
The printed output should look like a real invoice on white paper.
Save draft to localStorage + polish
Add a Save Draft button that serializes the entire form state — including all line items — to localStorage. On page load, restore that state if it exists. Then review the edge cases: empty fields, single line item, zero tax, large line item counts.
Add saveForm() that stores all field values and the line items
array to localStorage as a JSON object. Add loadForm() that
runs on page init — if saved data exists, populate all fields
and recreate line item rows. If no saved data, load a set of
default example values so the invoice looks complete from the
first visit.
Concepts You Just Used
oninput/onchange— fire a function every time a form field changesparseFloat(value || 0)— safely convert a possibly-empty string to a numberarray.reduce((sum, item) => sum + item.value, 0)— sum an array of line item amountsnumber.toFixed(2)— format a number to exactly 2 decimal places- Template literals (
`...${expression}...`) — build complex HTML strings with embedded logic element.innerHTML = htmlString— replace the entire contents of a containerwindow.open() + window.print()— trigger the browser’s native print/PDF dialogJSON.stringify / JSON.parsewith localStorage — persist a complex object between sessions
This chapter introduced reactive rendering — the idea that the UI is always a function of the current data. You didn’t manually update individual DOM elements; you re-rendered the whole output from scratch on every change. This is exactly how React, Vue, and Svelte work — just with more machinery around it.
Reflect
- What’s the difference between updating one DOM element and re-rendering the whole invoice on every change? What are the tradeoffs?
- How did you handle the case where a field is empty vs. zero? Is there a difference?
- The print export duplicates the CSS. What’s a cleaner way to handle this that you could prompt AI to implement?
- If you were building this for a client who needed to send 50 invoices a month, what would you add first?
- What surprised you about how simple the PDF export was? What can’t it do that a real PDF library could?
The reactive render pattern you built here is the foundation of every modern UI framework. You built it from scratch — which means you’ll understand those frameworks faster than anyone who learned them without this context.
Go Further
📩 Email Draft
Add a button that opens a mailto: link pre-filled with the client’s email and a summary of the invoice total.
📄 Multiple Invoices
Add a saved invoice list — each save creates a new numbered record in localStorage. Browse and reload past invoices.
🏭 Logo Upload
Let the user upload a logo image that appears in the invoice header. Store it as a base64 string in localStorage.
💰 Recurring Items
Save a library of frequently-used line items. One click inserts them into the current invoice.
🌐 Multi-Currency
Use the Fetch API to pull live exchange rates and convert the total into a second currency on the invoice.
📋 Copy as Text
Add a button that copies the invoice as plain text to the clipboard — useful for pasting into emails or Slack.
Animated Portfolio Page
Build a personal portfolio with scroll-triggered animations, a project grid, and a contact form — all deployable on GitHub Pages. You’ll learn CSS animations, Intersection Observer, and layout design.
Start Chapter 04 →