People and payroll / A4
Payslip HTML template with CSS
A statement of pay for one period: gross made of its parts, deductions made of theirs, the net that was actually paid, and the year to date.
When this document is the right one
Payroll runs. The employee reads it to check one number against last month, and later hands it to a lender or a landlord who needs a document rather than a screenshot.
Why each part is there
The fields are visible in the markup. What is not visible is why they are present, which is the part that gets removed first when someone adapts a template for their own use.
- Payments and deductions as two parallel columns, because that is how a payslip is read: two lists that must reconcile at the bottom.
- The basis for each variable line, such as hours and rate, so an unexpected number can be checked rather than queried.
- Net pay set apart and large, since it is the number that gets compared against the bank account.
- Year to date figures, which is what the employee actually verifies against and the most common complaint about generated payslips that omit them.
- Tax code and identification number, because a wrong tax code is the most common cause of a wrong payslip and the employee is the one who spots it.
The template
A complete file. Doctype, stylesheet, body: paste it into a renderer unchanged and it produces a finished A4 page. Replace the sample data and the styling holds.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Payslip, May 2026</title>
<style>
@page { size: A4; margin: 16mm; }
* { box-sizing: border-box; }
body { margin: 0; font: 9pt/1.4 Arial, sans-serif; color: #111; }
header { display: flex; justify-content: space-between; align-items: flex-start;
border-bottom: 2px solid #111; padding-bottom: 5mm; }
h1 { margin: 0; font-size: 15pt; }
.period { text-align: right; }
.period strong { font-size: 11pt; }
.employee { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5mm;
margin: 6mm 0; padding: 4mm 0; border-bottom: 1px solid #ddd; }
.employee .k { font-size: 7.5pt; text-transform: uppercase; letter-spacing: 0.06em;
color: #666; }
.employee .v { margin-top: 1mm; }
/* Two columns because a payslip is read as two lists that must agree at the
bottom: what was earned and what was taken off. */
.cols { display: grid; grid-template-columns: 1fr 1fr; gap: 8mm; }
h2 { font-size: 8.5pt; text-transform: uppercase; letter-spacing: 0.07em;
border-bottom: 1.5px solid #111; padding-bottom: 1.5mm; margin: 0 0 2mm; }
.row { display: flex; justify-content: space-between; padding: 1.6mm 0;
border-bottom: 1px solid #f0f0f0; }
.row span:last-child { font-variant-numeric: tabular-nums; }
.row.sum { border-bottom: 0; border-top: 1.5px solid #111; margin-top: 1.5mm;
padding-top: 2mm; font-weight: 700; }
.row small { display: block; color: #777; font-size: 7.5pt; }
.net { margin-top: 7mm; border: 2px solid #111; padding: 4mm 5mm;
display: flex; justify-content: space-between; align-items: center;
break-inside: avoid; }
.net .label { font-size: 9pt; text-transform: uppercase; letter-spacing: 0.08em; }
.net .value { font-size: 20pt; font-weight: 700; font-variant-numeric: tabular-nums; }
/* Year to date is what the employee actually checks against last month.
Leaving it off is the most common complaint about a generated payslip. */
.ytd { margin-top: 7mm; break-inside: avoid; }
.ytd table { width: 100%; border-collapse: collapse; }
.ytd th, .ytd td { border: 1px solid #ddd; padding: 2mm; text-align: right;
font-variant-numeric: tabular-nums; }
.ytd th { background: #f4f4f4; font-size: 7.5pt; text-transform: uppercase;
letter-spacing: 0.05em; color: #555; }
.ytd th:first-child, .ytd td:first-child { text-align: left; }
footer { margin-top: 8mm; font-size: 7.5pt; color: #666; border-top: 1px solid #ddd;
padding-top: 3mm; }
</style>
</head>
<body>
<header>
<div>
<h1>Kilnworks Studio Ltd</h1>
<div style="margin-top:1.5mm">PAYE reference 083/KW44712</div>
</div>
<div class="period">
<strong>Payslip</strong><br>
Period 2, May 2026<br>
Paid 28 May 2026
</div>
</header>
<section class="employee">
<div><div class="k">Employee</div><div class="v">S. Whitlock</div></div>
<div><div class="k">Payroll number</div><div class="v">KW-0142</div></div>
<div><div class="k">Tax code</div><div class="v">1257L</div></div>
<div><div class="k">NI number</div><div class="v">QQ 12 34 56 C</div></div>
</section>
<div class="cols">
<div>
<h2>Payments</h2>
<div class="row"><span>Basic salary<small>Annual 38,400, monthly</small></span><span>3,200.00</span></div>
<div class="row"><span>Overtime<small>6.5 hours at 1.5x</small></span><span>180.29</span></div>
<div class="row"><span>Studio allowance</span><span>75.00</span></div>
<div class="row sum"><span>Gross pay</span><span>3,455.29</span></div>
</div>
<div>
<h2>Deductions</h2>
<div class="row"><span>Income tax (PAYE)</span><span>481.06</span></div>
<div class="row"><span>National Insurance<small>Category A</small></span><span>236.42</span></div>
<div class="row"><span>Pension<small>5% qualifying earnings</small></span><span>149.26</span></div>
<div class="row"><span>Cycle to work<small>11 of 12</small></span><span>62.50</span></div>
<div class="row sum"><span>Total deductions</span><span>929.24</span></div>
</div>
</div>
<div class="net">
<div class="label">Net pay, to account ending 4021</div>
<div class="value">GBP 2,526.05</div>
</div>
<section class="ytd">
<table>
<tr>
<th>Year to date</th><th>Gross</th><th>Taxable</th>
<th>Tax</th><th>NI</th><th>Pension</th>
</tr>
<tr>
<td>2026 to 2027</td><td>6,910.58</td><td>6,611.06</td>
<td>962.12</td><td>472.84</td><td>298.52</td>
</tr>
<tr>
<td>Employer pension</td><td colspan="5">179.11 this period, 358.22 year to date</td>
</tr>
</table>
</section>
<footer>
This payslip is a record of pay and deductions for the period shown. Keep it:
it is the document a lender, a landlord or a tax enquiry will ask for, and a
replacement takes several working days. Queries to payroll@kilnworks.example
within 30 days of the pay date.
</footer>
</body>
</html>The layout decision worth understanding
The net pay block is bordered and carries break-inside: avoid. It is one line of content and it is the line the whole document exists to state, so it is given a frame rather than being another row in a table.
The mistake people make adapting it
Leaving off the employer pension contribution because the employee does not receive it. It is part of what the job pays, it appears in every conversation about total reward, and its absence makes the payslip look like it is hiding something it is not.
Rendering it
The page box is declared in the template's own CSS as A4, so the size travels with the markup rather than living in the code that calls the renderer. A4 is also one of the six named formats the API accepts, so it can be passed in the request options instead if that suits your setup better.
curl -X POST https://api.pdfpipe.xyz/v1/pdf \
-H "Authorization: Bearer $PDFPIPE_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<!doctype html>...",
"options": { "format": "A4" }
}' \
--output payslip.pdfFrequently asked
Can I use this template commercially?
Yes. Copy it, change it, ship it. It is sample markup written to be taken, and the sample data in it is invented, so replace the names and the numbers before anyone sees it.
Why is the page size in the CSS rather than in the API call?
Because a payslip is a A4 document, and that is a property of the document rather than of the code that renders it. Declaring it in the page box means the size cannot be lost between the template and the call, which is the usual way a document ends up on the wrong paper.
Will it look the same in every renderer?
The layout will, because it uses ordinary CSS. The page break behaviour will not: break-inside and break-after are implemented differently by browser engines and by dedicated print engines, and some of the paged media specification is not implemented in browser renders at all. That difference is documented per property in the paged CSS reference.
Other templates
Documents from the same part of a business, and one from each of the other groups.
Weekly timesheet PDF template
Hours by project and by day across a week, totalled both ways, with an approval signature.
Expense report HTML template
An itemised claim for reimbursement: expenses with receipts and business purposes, mileage calculated separately, and a policy note explaining the rules being applied.
Employment offer letter template
A letter offering a role, stating the principal terms in a scannable block, the conditions the offer depends on, and an acceptance section to sign and return.
HTML invoice template with CSS
The standard commercial invoice: a demand for payment with line items, a tax figure, and terms that say when the money is due.
Payment receipt HTML template
Proof that a payment was made: the amount, the payment reference, the method, and what it was for.
Quotation HTML template with CSS
A priced proposal with a validity date, a scope, optional items kept out of the headline total, and somewhere to sign.
Packing slip HTML template
The document that goes in the box: what was ordered, what was actually shipped, and no prices at all.
Every template
The full set, grouped by what part of a business produces the document.
A4 page size for PDF generation
A4 is the default page for business documents everywhere except North America, and the default this API uses when no format is given.
Paste the template into the playground and get the PDF back. No signup, no key.