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:

PositionLocation
@top-leftTop margin, left side
@top-centerTop margin, center
@top-rightTop margin, right side
@bottom-leftBottom margin, left side
@bottom-centerBottom margin, center
@bottom-rightBottom margin, right side
@left-topLeft margin, top
@left-middleLeft margin, middle
@left-bottomLeft margin, bottom
@right-topRight margin, top
@right-middleRight margin, middle
@right-bottomRight 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()

Featurestring-set / string()running() / element()
Plain textYesYes
Images / logosNoYes
Styled multi-element HTMLNoYes
Simpler syntaxYesNo

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;
}

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;
  }
}
@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;
  }
}
@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 kaleido package for static export: pip install plotly kaleido

Other Prerendering Approaches

ApproachDescription
Server-side SVGGenerate SVG strings with D3 (Node.js) or matplotlib (Python) and embed inline
JS library exportUse Chart.js .toBase64Image(), Plotly .toImage(), or D3 .node().outerHTML before sending HTML to the API
Static image APIUse services like QuickChart.io to generate chart images via URL
matplotlib / seabornSave 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; set font-family and font-size explicitly
  • SVG sizing — Always use viewBox for responsive scaling; avoid fixed width/height in px
  • Page breaks — Wrap charts in a container with break-inside: avoid to 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; }
PropertyValuesDescription
-pdfmcp-bookmark-levelnone, 16Depth in the PDF outline
-pdfmcp-bookmark-labelcontent(text), content(before), content(), custom stringDisplay text
-pdfmcp-bookmark-stateopen, closedWhether 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:

FeatureSupportNotes
CSS GridNot supportedUse <table> or float-based layouts
FlexboxPartialBasic flex works; avoid for page-level structure
CSS VariablesSupportedvar(--color) works normally
GradientsSupportedlinear-gradient, radial-gradient
calc()SupportedWorks in all contexts
2D TransformsSupportedrotate(), scale(), translate()
3D TransformsNot supportedNo perspective, rotateX/Y/Z
Box ShadowSupportedWorks normally
Border RadiusSupportedWorks normally
OpacitySupportedWorks normally
AnimationsNot supportedStatic rendering only
JavaScriptNot executedNo <script>, no dynamic rendering
position: fixedNot supportedUse position: running() for repeated elements
position: stickyNot supportedUse thead { display: table-header-group; }
Viewport unitsNot supportedNo 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

  1. Test Early, Test Often - Preview your PDF output frequently during development
  2. Use Points for Print - Use pt units for fonts and cm or in for margins
  3. Keep It Simple - Complex layouts may not translate well to paginated output
  4. 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

IssueSolution
Content cut off at page edgesSet adequate page margins
Headers/footers overlap contentEnsure margin boxes don’t exceed margin size
Tables split awkwardlyUse display: table-header-group on thead
Images overflow pagesSet max-width: 100% and page-break-inside: avoid
Fonts not renderingUse web-safe fonts or embed via @font-face
Colors not printingAdd 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

SizeDimensions (mm)Dimensions (inches)
A4210 x 2978.27 x 11.69
A5148 x 2105.83 x 8.27
Letter216 x 2798.5 x 11
Legal216 x 3568.5 x 14
Tabloid279 x 43211 x 17