Master the art of creating professional PDF documents from HTML and CSS. This guide covers essential techniques for page layout, typography, and print-specific styling to ensure your PDFs look exactly as intended.
Overview
When converting HTML to PDF, the rendering engine interprets your HTML and CSS to produce paginated output. Understanding how CSS handles print layouts is essential for creating professional documents like invoices, reports, contracts, and certificates.
Page Layout with @page Rules
The @page CSS at-rule controls the overall page structure of your PDF document.
Setting Page Size
/* Standard paper sizes */
@page {
size: A4; /* 210mm x 297mm */
}
@page {
size: Letter; /* 8.5in x 11in */
}
@page {
size: Legal; /* 8.5in x 14in */
}
/* Custom dimensions */
@page {
size: 8in 10in; /* Width x Height */
}
/* Landscape orientation */
@page {
size: A4 landscape;
}
Setting Margins
Page margins control the printable area and space for headers/footers:
@page {
size: A4;
margin: 2cm; /* All sides equal */
}
@page {
size: Letter;
margin: 1in 0.75in; /* Vertical, Horizontal */
}
@page {
size: A4;
margin: 2cm 1.5cm 2cm 1.5cm; /* Top, Right, Bottom, Left */
}
Named Pages
Use named pages for different sections of your document:
@page cover {
margin: 0;
}
@page chapter {
margin: 2cm;
@top-center {
content: "Chapter Title";
}
}
.cover-page {
page: cover;
}
.chapter-content {
page: chapter;
}
First, Left, and Right Pages
Apply different styles to specific page positions:
/* First page - often no header */
@page :first {
@top-center {
content: none;
}
}
/* Left pages (even) */
@page :left {
margin-left: 3cm;
margin-right: 2cm;
}
/* Right pages (odd) */
@page :right {
margin-left: 2cm;
margin-right: 3cm;
}
Page Breaks
Control how content flows across pages to prevent awkward breaks and keep related content together.
Forcing Page Breaks
/* Break before an element */
.chapter-title {
page-break-before: always;
break-before: page;
}
/* Break after an element */
.section-end {
page-break-after: always;
break-after: page;
}
<!-- Force a new page before this element -->
<h1 class="chapter-title">Chapter 2: Advanced Topics</h1>
<!-- Force a new page after this element -->
<div class="section-end">End of Section 1</div>
Preventing Page Breaks
Keep related content together by preventing breaks inside elements:
/* Prevent breaks inside an element */
.keep-together {
page-break-inside: avoid;
break-inside: avoid;
}
/* Common elements to keep together */
figure,
blockquote,
table,
.card,
.info-box {
page-break-inside: avoid;
break-inside: avoid;
}
Orphans and Widows
Control minimum lines at page boundaries:
p {
orphans: 3; /* Minimum lines at bottom of page */
widows: 3; /* Minimum lines at top of page */
}
Best Practices for Page Breaks
/* Headings should stay with following content */
h1, h2, h3, h4, h5, h6 {
page-break-after: avoid;
break-after: avoid;
}
/* Don't break inside list items */
li {
page-break-inside: avoid;
break-inside: avoid;
}
/* Keep images with captions */
figure {
page-break-inside: avoid;
break-inside: avoid;
}
Headers and Footers
Add running headers and footers using CSS margin boxes within the @page rule.
Basic Headers and Footers
@page {
size: A4;
margin: 2.5cm 2cm;
@top-left {
content: "Company Name";
font-size: 10pt;
color: #666;
}
@top-right {
content: "Confidential";
font-size: 10pt;
color: #666;
}
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 9pt;
}
}
Available Margin Boxes
The following margin box positions are available:
| Position | Location |
|---|---|
@top-left | Top margin, left side |
@top-center | Top margin, center |
@top-right | Top margin, right side |
@bottom-left | Bottom margin, left side |
@bottom-center | Bottom margin, center |
@bottom-right | Bottom margin, right side |
@left-top | Left margin, top |
@left-middle | Left margin, middle |
@left-bottom | Left margin, bottom |
@right-top | Right margin, top |
@right-middle | Right margin, middle |
@right-bottom | Right margin, bottom |
Page Counters
Use CSS counters for page numbering:
@page {
@bottom-right {
content: "Page " counter(page);
}
}
/* With total page count */
@page {
@bottom-center {
content: counter(page) " / " counter(pages);
}
}
Dynamic Content in Headers
Use CSS string-set to create running headers:
h1 {
string-set: chapter-title content();
}
@page {
@top-left {
content: string(chapter-title);
}
}
Different Headers for First Page
@page :first {
@top-left {
content: none; /* No header on first page */
}
@top-right {
content: none;
}
}
@page {
@top-center {
content: "Document Title";
}
}
Running Elements (Rich HTML in Headers/Footers)
The content property in margin boxes is limited to strings, counters, and url(). When you need rich HTML in your headers or footers — logos alongside text, styled layouts, or multi-element content — use position: running() combined with element().
How it works: You place an HTML element in your document, mark it with position: running(name) to pull it out of the normal flow, then reference it with content: element(name) in a margin box.
/* Pull element out of the flow */
.running-header {
position: running(headerContent);
}
/* Place it in the page margin */
@page {
margin-top: 3cm;
@top-center {
content: element(headerContent);
}
}
<!-- This won't appear inline — it goes straight to the margin box -->
<div class="running-header">
<img src="https://example.com/logo.png" style="height: 20px; vertical-align: middle;" />
<span style="font-size: 10pt; color: #666; margin-left: 8pt;">Company Name</span>
</div>
<h1>Document Title</h1>
<p>Content starts here...</p>
Key behaviors:
- The running element is removed from the document flow — it won’t appear in the page content
- It renders in the margin box on every page
- If another element with the same running name appears later in the document, it replaces the previous one from that page onward
This replacement behavior is ideal for chapter-specific headers:
.chapter-header { position: running(chapterHead); }
@page {
@top-left {
content: element(chapterHead);
}
}
<section>
<div class="chapter-header">Chapter 1: Introduction</div>
<p>Chapter 1 content...</p>
</section>
<section style="break-before: page;">
<!-- Replaces the running header from this page onward -->
<div class="chapter-header">Chapter 2: Analysis</div>
<p>Chapter 2 content...</p>
</section>
Comparison: string-set vs running()
| Feature | string-set / string() | running() / element() |
|---|---|---|
| Plain text | Yes | Yes |
| Images / logos | No | Yes |
| Styled multi-element HTML | No | Yes |
| Simpler syntax | Yes | No |
Use string-set when you only need text (e.g. chapter titles). Use running() when you need logos, icons, or any styled HTML in your headers/footers.
Font Handling
Typography is critical for professional PDF output. Here’s how to ensure your fonts render correctly.
Web-Safe Fonts
For maximum compatibility, use web-safe fonts:
body {
font-family: Arial, Helvetica, sans-serif;
}
.monospace {
font-family: "Courier New", Courier, monospace;
}
.serif {
font-family: Georgia, "Times New Roman", serif;
}
Using Custom Fonts
Embed fonts using @font-face:
@font-face {
font-family: 'CustomFont';
src: url('https://example.com/fonts/custom.woff2') format('woff2'),
url('https://example.com/fonts/custom.woff') format('woff');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'CustomFont', sans-serif;
}
Google Fonts via CDN
Link to Google Fonts directly in your HTML:
<!DOCTYPE html>
<html>
<head>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Roboto', sans-serif;
}
</style>
</head>
<body>
<h1>Your content</h1>
</body>
</html>
Base64 Embedded Fonts
For offline reliability, embed fonts as base64:
@font-face {
font-family: 'EmbeddedFont';
src: url(data:font/woff2;base64,d09GMgABAAAAAA...) format('woff2');
}
Font Best Practices
body {
/* Use a font stack with fallbacks */
font-family: 'Primary Font', 'Fallback Font', sans-serif;
/* Set a readable base size */
font-size: 11pt;
/* Ensure good line spacing */
line-height: 1.5;
/* Enable font smoothing */
-webkit-font-smoothing: antialiased;
}
/* Optimize headings */
h1, h2, h3 {
line-height: 1.2;
page-break-after: avoid;
}
Print Media Queries
Use @media print to apply styles specifically for PDF output:
Basic Print Styles
/* Screen-only styles */
@media screen {
.print-only {
display: none;
}
body {
background: #f5f5f5;
}
}
/* Print-only styles */
@media print {
.no-print,
.screen-only {
display: none !important;
}
body {
background: white;
color: black;
}
/* Remove decorative elements */
nav,
.sidebar,
.ads,
.social-share {
display: none !important;
}
}
Optimizing Links for Print
@media print {
/* Show URL after links */
a[href^="http"]:after {
content: " (" attr(href) ")";
font-size: 0.8em;
color: #666;
}
/* But not for image links or internal links */
a[href^="#"]:after,
a:has(img):after {
content: none;
}
/* Remove link styling */
a {
color: inherit;
text-decoration: underline;
}
}
Print-Specific Layout Adjustments
@media print {
/* Use full width */
.container {
max-width: 100%;
margin: 0;
padding: 0;
}
/* Flatten multi-column layouts */
.grid {
display: block;
}
/* Ensure images don't overflow */
img {
max-width: 100%;
height: auto;
}
/* Reset background colors */
* {
background: transparent !important;
}
}
Table Handling
Tables often require special attention in PDFs to handle page breaks gracefully.
Basic Table Styling
table {
width: 100%;
border-collapse: collapse;
page-break-inside: auto;
}
thead {
display: table-header-group; /* Repeat header on each page */
}
tfoot {
display: table-footer-group; /* Repeat footer on each page */
}
tbody {
display: table-row-group;
}
tr {
page-break-inside: avoid; /* Keep rows together */
break-inside: avoid;
}
Repeating Table Headers
Ensure headers repeat on each page:
<table>
<thead>
<tr>
<th>Column 1</th>
<th>Column 2</th>
<th>Column 3</th>
</tr>
</thead>
<tbody>
<tr><td>Data 1</td><td>Data 2</td><td>Data 3</td></tr>
<!-- More rows... -->
</tbody>
</table>
thead {
display: table-header-group;
background: #f0f0f0;
}
th {
font-weight: bold;
text-align: left;
padding: 8px;
border-bottom: 2px solid #333;
}
Handling Long Tables
/* Allow table to break across pages */
table.long-table {
page-break-inside: auto;
}
/* But keep individual rows together */
table.long-table tr {
page-break-inside: avoid;
break-inside: avoid;
}
/* Minimum height prevents tiny orphan rows */
table.long-table td {
min-height: 2em;
}
Zebra Striping for Print
tbody tr:nth-child(even) {
background-color: #f9f9f9;
}
@media print {
tbody tr:nth-child(even) {
background-color: #f0f0f0 !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
Table Border Handling
table {
border: 1px solid #ccc;
}
th, td {
border: 1px solid #ccc;
padding: 8px 12px;
}
/* Handle borders at page breaks */
@media print {
table {
border-collapse: collapse;
}
tr {
border-bottom: 1px solid #ccc;
}
}
Charts and Data Visualizations
The PDF rendering engine does not execute JavaScript, which means popular chart libraries like Chart.js, D3.js, Plotly.js, Highcharts, and ApexCharts will not produce any output. <canvas> elements will render as blank. To include charts in your PDFs, you must prerender them as static SVG or images.
Using Inline SVG
SVG is the recommended format — it produces crisp, scalable vector graphics that look great at any zoom level:
<div class="chart">
<h4>Revenue by Quarter</h4>
<svg viewBox="0 0 400 220" xmlns="http://www.w3.org/2000/svg">
<rect x="30" y="120" width="50" height="80" fill="#3498db" rx="2"/>
<rect x="110" y="60" width="50" height="140" fill="#3498db" rx="2"/>
<rect x="190" y="30" width="50" height="170" fill="#2ecc71" rx="2"/>
<rect x="270" y="50" width="50" height="150" fill="#2ecc71" rx="2"/>
<text x="55" y="215" text-anchor="middle" font-size="11" fill="#333">Q1</text>
<text x="135" y="215" text-anchor="middle" font-size="11" fill="#333">Q2</text>
<text x="215" y="215" text-anchor="middle" font-size="11" fill="#333">Q3</text>
<text x="295" y="215" text-anchor="middle" font-size="11" fill="#333">Q4</text>
<line x1="20" y1="200" x2="340" y2="200" stroke="#666" stroke-width="1"/>
<line x1="20" y1="0" x2="20" y2="200" stroke="#666" stroke-width="1"/>
</svg>
</div>
Using Embedded Images
When SVG isn’t available, use base64-encoded or hosted images:
<!-- Base64 encoded -->
<img src="data:image/png;base64,iVBORw0KGgo..."
alt="Revenue Chart"
style="width: 100%; max-width: 600px;" />
<!-- Hosted URL -->
<img src="https://example.com/charts/revenue-q4.png"
alt="Revenue Chart"
style="width: 100%; max-width: 600px;" />
Styling Charts for Print
.chart {
break-inside: avoid;
page-break-inside: avoid;
margin: 1cm 0;
text-align: center;
}
.chart svg,
.chart img {
max-width: 100%;
height: auto;
}
/* SVG text doesn't inherit document fonts */
.chart svg text {
font-family: Arial, Helvetica, sans-serif;
fill: #333;
}
Generating Charts with Plotly (Python)
If you can run Python (e.g. in a script, server, or code interpreter), Plotly is an excellent choice for generating publication-quality charts that export as static SVG without requiring a browser:
import plotly.graph_objects as go
import plotly.io as pio
import base64
# Create a chart
fig = go.Figure(data=[
go.Bar(x=["Q1", "Q2", "Q3", "Q4"], y=[120, 180, 240, 200],
marker_color="#3498db")
])
fig.update_layout(
title="Quarterly Revenue",
width=600, height=350,
margin=dict(l=40, r=20, t=50, b=40),
font=dict(family="Arial, sans-serif", size=12)
)
# Export as inline SVG (preferred)
svg_str = pio.to_image(fig, format="svg").decode("utf-8")
# Or as base64 PNG
png_b64 = base64.b64encode(
pio.to_image(fig, format="png", scale=2)
).decode("utf-8")
Then embed in your HTML template:
<!-- SVG (preferred — sharp at any resolution) -->
<div class="chart">{svg_str}</div>
<!-- Or base64 PNG -->
<div class="chart">
<img src="data:image/png;base64,{png_b64}"
alt="Quarterly Revenue"
style="width:100%; max-width:600px;" />
</div>
Note: Plotly requires the
kaleidopackage for static export:pip install plotly kaleido
Other Prerendering Approaches
| Approach | Description |
|---|---|
| Server-side SVG | Generate SVG strings with D3 (Node.js) or matplotlib (Python) and embed inline |
| JS library export | Use Chart.js .toBase64Image(), Plotly .toImage(), or D3 .node().outerHTML before sending HTML to the API |
| Static image API | Use services like QuickChart.io to generate chart images via URL |
| matplotlib / seaborn | Save as SVG with plt.savefig(buf, format='svg') and embed the SVG string |
Common Chart Pitfalls
- Blank canvas —
<canvas>requires JavaScript and will not render; use SVG or<img>instead - SVG font issues — SVG
<text>elements don’t inherit document fonts; setfont-familyandfont-sizeexplicitly - SVG sizing — Always use
viewBoxfor responsive scaling; avoid fixedwidth/heightin px - Page breaks — Wrap charts in a container with
break-inside: avoidto prevent splitting - Colors — Use high-contrast, print-friendly colors; avoid relying on transparency or subtle gradients
PDF Bookmarks
PDF bookmarks create a clickable outline in the PDF viewer’s sidebar, letting readers jump between sections. Use -pdfmcp-bookmark-* properties to build the outline from your headings:
/* Map headings to bookmark levels */
h1 { -pdfmcp-bookmark-level: 1; -pdfmcp-bookmark-state: open; }
h2 { -pdfmcp-bookmark-level: 2; -pdfmcp-bookmark-state: closed; }
h3 { -pdfmcp-bookmark-level: 3; }
/* Custom label text */
h1 { -pdfmcp-bookmark-label: content(text); }
/* Custom label for appendix sections */
.appendix h2 {
-pdfmcp-bookmark-level: 1;
-pdfmcp-bookmark-label: "Appendix: " content(text);
}
/* Exclude decorative headings */
.no-bookmark { -pdfmcp-bookmark-level: none; }
| Property | Values | Description |
|---|---|---|
-pdfmcp-bookmark-level | none, 1–6 | Depth in the PDF outline |
-pdfmcp-bookmark-label | content(text), content(before), content(), custom string | Display text |
-pdfmcp-bookmark-state | open, closed | Whether children are expanded |
Cross-References
Use target-counter() to insert the page number of any element by its id. This enables “see page X” references and is the foundation for tables of contents:
a.page-ref::after {
content: " (page " target-counter(attr(href), page) ")";
}
<p>For more details, see <a class="page-ref" href="#results">Results</a>.</p>
<!-- Renders: "For more details, see Results (page 12)." -->
Footnotes
CSS footnotes automatically move marked content to the bottom of the current page with auto-numbering:
.footnote { float: footnote; }
::footnote-call {
content: counter(footnote);
font-size: 0.7em;
vertical-align: super;
line-height: none;
}
::footnote-marker {
content: counter(footnote) ". ";
font-weight: bold;
}
/* Optional rule above footnotes */
@page {
@footnote {
border-top: 1px solid #ccc;
padding-top: 0.5em;
}
}
<p>Studies confirm this finding.<span class="footnote">Smith et al., 2024.</span></p>
The footnote text moves to the page bottom with automatic superscript numbering in both places.
Hyphenation
Automatic hyphenation improves justified text by breaking long words at syllable boundaries:
body {
hyphens: auto;
-webkit-hyphens: auto;
}
Requirement: Set the lang attribute on <html> for the hyphenation dictionary to load:
<html lang="en">
Works well combined with text-align: justify for a clean, professional look.
Preserve Background Colors
Background colors are stripped by default in print rendering. Force them to appear with print-color-adjust:
body {
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
/* Or target specific elements */
.colored-badge {
background: #e74c3c;
color: white;
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
}
Box Decoration Break
When an element with borders or backgrounds splits across a page boundary, box-decoration-break: clone repeats the full decoration on each fragment:
.info-box {
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
border: 2px solid #3498db;
padding: 1em;
background: #f0f8ff;
}
Without clone (the default slice behavior), the top fragment loses its bottom border and the bottom fragment loses its top border at the page break.
Table of Contents with Leaders
Combine leader() and target-counter() to create classic TOC entries with dot fills:
.toc a {
text-decoration: none;
color: inherit;
display: block;
}
.toc a::after {
content: leader(".") target-counter(attr(href), page);
}
.toc .level-2 { padding-left: 1.5em; }
.toc .level-3 { padding-left: 3em; }
<nav class="toc">
<a class="level-1" href="#intro">Introduction</a>
<a class="level-2" href="#background">Background</a>
<a class="level-1" href="#methods">Methods</a>
</nav>
This renders as:
Introduction ............ 1
Background ............ 3
Methods ................. 5
You can use different fill characters: leader("."), leader("-"), leader("_"), or leader(" ").
CSS Support Caveats
Not all CSS features work in paged PDF rendering. Here’s what to expect:
| Feature | Support | Notes |
|---|---|---|
| CSS Grid | Not supported | Use <table> or float-based layouts |
| Flexbox | Partial | Basic flex works; avoid for page-level structure |
| CSS Variables | Supported | var(--color) works normally |
| Gradients | Supported | linear-gradient, radial-gradient |
calc() | Supported | Works in all contexts |
| 2D Transforms | Supported | rotate(), scale(), translate() |
| 3D Transforms | Not supported | No perspective, rotateX/Y/Z |
| Box Shadow | Supported | Works normally |
| Border Radius | Supported | Works normally |
| Opacity | Supported | Works normally |
| Animations | Not supported | Static rendering only |
| JavaScript | Not executed | No <script>, no dynamic rendering |
position: fixed | Not supported | Use position: running() for repeated elements |
position: sticky | Not supported | Use thead { display: table-header-group; } |
| Viewport units | Not supported | No vw/vh — use mm, cm, in, pt |
Key takeaway: For layout, stick to block/inline, floats, tables, and basic flexbox. Use CSS Paged Media properties (@page, break-*, margin boxes) for print-specific behavior.
Complete Example: Invoice Template
Here’s a complete example combining all techniques:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Invoice #12345</title>
<style>
@page {
size: A4;
margin: 2cm 1.5cm;
@top-right {
content: "Invoice #12345";
font-size: 9pt;
color: #666;
}
@bottom-center {
content: "Page " counter(page) " of " counter(pages);
font-size: 8pt;
color: #999;
}
}
@page :first {
@top-right {
content: none;
}
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
font-size: 11pt;
line-height: 1.5;
color: #333;
}
.header {
display: flex;
justify-content: space-between;
margin-bottom: 2cm;
page-break-inside: avoid;
}
.company-info h1 {
font-size: 24pt;
color: #2563eb;
margin-bottom: 0.5em;
}
.invoice-details {
text-align: right;
}
.invoice-number {
font-size: 14pt;
font-weight: bold;
}
.addresses {
display: flex;
justify-content: space-between;
margin-bottom: 1.5cm;
}
.address-block {
width: 45%;
}
.address-block h3 {
font-size: 10pt;
text-transform: uppercase;
color: #666;
margin-bottom: 0.5em;
border-bottom: 1px solid #ddd;
padding-bottom: 0.25em;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1cm;
}
thead {
display: table-header-group;
}
th {
background: #f8f9fa;
font-weight: 600;
text-align: left;
padding: 12px;
border-bottom: 2px solid #dee2e6;
}
td {
padding: 12px;
border-bottom: 1px solid #eee;
}
tr {
page-break-inside: avoid;
}
.text-right {
text-align: right;
}
.totals {
margin-left: auto;
width: 250px;
page-break-inside: avoid;
}
.totals-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.totals-row.total {
font-weight: bold;
font-size: 14pt;
border-bottom: 2px solid #333;
border-top: 2px solid #333;
padding: 12px 0;
}
.notes {
margin-top: 2cm;
padding: 1cm;
background: #f8f9fa;
border-radius: 4px;
page-break-inside: avoid;
}
.notes h4 {
margin-bottom: 0.5em;
}
@media print {
.no-print {
display: none;
}
}
</style>
</head>
<body>
<div class="header">
<div class="company-info">
<h1>ACME Corp</h1>
<p>123 Business Street</p>
<p>City, State 12345</p>
<p>contact@acme.com</p>
</div>
<div class="invoice-details">
<p class="invoice-number">Invoice #12345</p>
<p>Date: January 15, 2025</p>
<p>Due: February 15, 2025</p>
</div>
</div>
<div class="addresses">
<div class="address-block">
<h3>Bill To</h3>
<p><strong>Client Company</strong></p>
<p>456 Client Avenue</p>
<p>City, State 67890</p>
</div>
<div class="address-block">
<h3>Ship To</h3>
<p><strong>Client Company</strong></p>
<p>456 Client Avenue</p>
<p>City, State 67890</p>
</div>
</div>
<table>
<thead>
<tr>
<th>Description</th>
<th>Qty</th>
<th class="text-right">Unit Price</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>Professional Services</td>
<td>10</td>
<td class="text-right">$150.00</td>
<td class="text-right">$1,500.00</td>
</tr>
<tr>
<td>Software License</td>
<td>1</td>
<td class="text-right">$499.00</td>
<td class="text-right">$499.00</td>
</tr>
<tr>
<td>Support Package</td>
<td>1</td>
<td class="text-right">$99.00</td>
<td class="text-right">$99.00</td>
</tr>
</tbody>
</table>
<div class="totals">
<div class="totals-row">
<span>Subtotal</span>
<span>$2,098.00</span>
</div>
<div class="totals-row">
<span>Tax (10%)</span>
<span>$209.80</span>
</div>
<div class="totals-row total">
<span>Total</span>
<span>$2,307.80</span>
</div>
</div>
<div class="notes">
<h4>Notes</h4>
<p>Payment is due within 30 days. Please include invoice number with your payment.</p>
</div>
</body>
</html>
Tips and Best Practices
General Guidelines
- Test Early, Test Often - Preview your PDF output frequently during development
- Use Points for Print - Use
ptunits for fonts andcmorinfor margins - Keep It Simple - Complex layouts may not translate well to paginated output
- Provide Fallbacks - Always include fallback fonts and graceful degradation
Performance Tips
- Inline Critical CSS - Avoid external stylesheet requests
- Optimize Images - Use appropriately sized images; base64 for small graphics
- Minimize External Resources - Each external resource adds latency
Accessibility
- Sufficient Contrast - Ensure text is readable when printed
- Readable Font Sizes - Minimum 10pt for body text
- Meaningful Structure - Use semantic HTML for better content flow
Common Pitfalls to Avoid
| Issue | Solution |
|---|---|
| Content cut off at page edges | Set adequate page margins |
| Headers/footers overlap content | Ensure margin boxes don’t exceed margin size |
| Tables split awkwardly | Use display: table-header-group on thead |
| Images overflow pages | Set max-width: 100% and page-break-inside: avoid |
| Fonts not rendering | Use web-safe fonts or embed via @font-face |
| Colors not printing | Add print-color-adjust: exact |
Quick Reference
Essential CSS Properties
/* Page setup */
@page { size: A4; margin: 2cm; }
/* Page breaks */
page-break-before: always | avoid | auto;
page-break-after: always | avoid | auto;
page-break-inside: avoid | auto;
/* Modern alternatives */
break-before: page | avoid | auto;
break-after: page | avoid | auto;
break-inside: avoid | auto;
/* Paragraph control */
orphans: 3;
widows: 3;
/* Table headers */
thead { display: table-header-group; }
/* Color printing */
print-color-adjust: exact;
-webkit-print-color-adjust: exact;
Common Page Sizes
| Size | Dimensions (mm) | Dimensions (inches) |
|---|---|---|
| A4 | 210 x 297 | 8.27 x 11.69 |
| A5 | 148 x 210 | 5.83 x 8.27 |
| Letter | 216 x 279 | 8.5 x 11 |
| Legal | 216 x 356 | 8.5 x 14 |
| Tabloid | 279 x 432 | 11 x 17 |
Related Documentation
- HTML to PDF - Convert HTML to PDF via API
- API Reference - Complete API documentation
- Quick Start - Get started in 5 minutes