Easy Invoice — Live PDF playground
Example on how to use Easyinvoice to create an invoice in the browser.
by Danny Veldhoen
HTML
<main class="demo">
<header class="topbar">
<a class="brand" href="https://www.budgetinvoice.com/" target="_blank" rel="noopener">
<span class="brand-mark">ei</span> Easy Invoice</a>
<span class="badge">
<i>
</i> LIVE PLAYGROUND</span>
</header>
<section class="intro">
<p class="eyebrow">FROM DETAILS TO DOWNLOAD</p>
<h1>A little paperwork.<br>
<em>A lot less work.</em>
</h1>
<p>Edit the sample, add your finishing touches, and create a real PDF invoice.</p>
</section>
<div class="workspace">
<form id="invoice-form" class="card editor">
<div class="section-title">
<span class="step">01</span>
<div>
<h2>Make it yours</h2>
<p>Everything is filled in. Just give it a try.</p>
</div>
<button type="button" id="reset-demo" class="text-button">Reset</button>
</div>
<div class="field-grid">
<label>Invoice number<input name="number" value="2026.0001" required maxlength="60">
</label>
<label>Currency<select name="currency">
<option value="USD">USD — US dollar</option>
<option value="EUR">EUR — Euro</option>
<option value="GBP">GBP — Pound sterling</option>
</select>
</label>
<label>Invoice date<input type="date" name="date" required>
</label>
<label>Due date<input type="date" name="dueDate" required>
</label>
</div>
<div class="parties">
<details>
<summary>
<span>FROM</span>
<strong id="sender-label">Sample Corp</strong>
<span class="chevron">+</span>
</summary>
<div class="party-fields">
<label>Company<input name="senderCompany" value="Sample Corp" required>
</label>
<label>Address<input name="senderAddress" value="123 Main Street" required>
</label>
<div class="field-grid">
<label>ZIP code<input name="senderZip" value="78701" required>
</label>
<label>City<input name="senderCity" value="Austin, TX" required>
</label>
</div>
<label>Country<input name="senderCountry" value="United States" required>
</label>
</div>
</details>
<details>
<summary>
<span>BILL TO</span>
<strong...
CSS
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&display=swap');
:root { color-scheme: light; --ink:#203230; --muted:#71817b; --accent:#c75836; --line:#e5e8e0; --cream:#f6f7f1; --green:#254b3e; }
* { box-sizing:border-box; }
body { margin:0; background:var(--cream); color:var(--ink); font:14px/1.5 'DM Sans',sans-serif; }
button,input,select,textarea { font:inherit; }
a { color:inherit; text-decoration:none; }
button,a,input,select,textarea,summary { -webkit-tap-highlight-color:transparent; }
button,a,summary { cursor:pointer; }
button:focus-visible,a:focus-visible,summary:focus-visible { outline:3px solid #c7583670; outline-offset:4px; }
.demo { max-width:1180px; padding:30px 36px 20px; margin:auto; }
.topbar { display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--line); padding-bottom:23px; }
.brand { display:flex; gap:10px; align-items:center; font-family:'Manrope',sans-serif; font-size:19px; font-weight:800; letter-spacing:-.7px; }
.brand-mark { display:grid; place-items:center; background:var(--green); color:#f9f5de; width:34px; height:34px; border-radius:10px; font-size:23px; letter-spacing:-2px; padding-right:3px; }
.badge { display:flex; align-items:center; gap:8px; font-size:9px; font-weight:700; letter-spacing:1.3px; color:var(--green); }
.badge i { width:6px; height:6px; background:#649670; border-radius:50%; box-shadow:0 0 0 4px #64967015; }
.intro { padding:34px 0 30px; }
.eyebrow { margin:0 0 12px; font-size:9px; font-weight:700; letter-spacing:2px; color:var(--accent); }
h1 { font:800 45px/1.1 'Manrope',sans-serif; letter-spacing:-2px; margin:0 0 14px; }
h1 em { color:var(--green); font-style:normal; }
.intro>p:last-child { margin:0; color:var(--muted); max-width:470px; font-size:14px; }
.workspace { display:grid; grid-template-columns:minmax(0,1.05fr) minmax(0,1fr); gap:22px; align-items:start; }
.card {...
JavaScript
// Complete sample invoice. Copy this function with fetchBase64() below.
async function getSampleData(includeArtwork = true) {
const data = {
mode: 'development',
sender: {
company: 'Sample Corp',
address: '123 Main Street',
zip: '78701',
city: 'Austin, TX',
country: 'United States'
},
client: {
company: 'Client Corp',
address: '456 Oak Avenue',
zip: '75201',
city: 'Dallas, TX',
country: 'United States'
},
information: {
number: '2026.0001',
date: '09/13/2026',
dueDate: '09/27/2026'
},
products: [
{ quantity: 2, description: 'Consulting', taxRate: 8.25, price: 75 },
{ quantity: 1, description: 'Project setup', taxRate: 8.25, price: 100 }
],
bottomNotice: 'Please pay within 14 days. Thank you for your business!',
settings: { currency: 'USD', locale: 'en-US', format: 'Letter' }
};
if (includeArtwork) {
data.images = {
logo: await fetchBase64(
'https://public.budgetinvoice.com/img/logo_en_original.png'
),
background: await fetchBase64(
'https://public.budgetinvoice.com/pdf/sample-background-no-logo.pdf'
)
};
}
return data;
}
// Minimal use:
// const data = await getSampleData();
// const invoice = await createInvoice(data);
// const bytes = await fetchPdf(invoice.pdfUrl);
// await renderPdf(bytes, document.querySelector('#pdf-preview'));
// Fetch logo or background bytes and convert them to base64.
async function fetchBase64(url) {
const response = await fetch(url, { signal: AbortSignal.timeout(15000) });
if (!response.ok) {
throw new Error('Artwork download failed (' + response.status + ').');
}
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result).split(',')[1]);
reader.onerror =...