pdf-mcp Documentation
Browse documentation
Markdown

Print styling guide

HTML and CSS patterns for predictable PDF layout and rendering

On this page

The PDF renderer turns HTML and CSS into paginated output. This guide covers page layout, typography, tables, charts, and print-specific CSS for invoices, reports, contracts, and certificates.


Page layout

The @page at-rule controls the document’s page structure.

Page size

CSS
/* 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;
}

Margins

Page margins control the printable area and space for headers/footers:

CSS
@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:

CSS
@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:

CSS
/* 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

Force page breaks

CSS
/* 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;
}
HTML
<!-- 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>

Prevent page breaks

Keep related content together by preventing breaks inside elements:

CSS
/* 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:

CSS
p {
  orphans: 3;   /* Minimum lines at bottom of page */
  widows: 3;    /* Minimum lines at top of page */
}

Page-break rules

CSS
/* 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

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

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:

CSS
@page {
  @bottom-right {
    content: "Page " counter(page);
  }
}

/* With total page count */
@page {
  @bottom-center {
    content: counter(page) " / " counter(pages);
  }
}

Dynamic header text

Use CSS string-set to create running headers:

CSS
h1 {
  string-set: chapter-title content();
}

@page {
  @top-left {
    content: string(chapter-title);
  }
}

First-page headers

CSS
@page :first {
  @top-left {
    content: none;    /* No header on first page */
  }
  @top-right {
    content: none;
  }
}

@page {
  @top-center {
    content: "Document Title";
  }
}

Running elements

The content property in margin boxes accepts strings, counters, and url(). For logos, styled layouts, or other HTML in a header or footer, combine position: running() with element().

Place an HTML element in the document, remove it from normal flow with position: running(name), and reference it from a margin box with content: element(name).

CSS
/* 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);
  }
}
HTML
<!-- 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>

Behavior:

  • The running element is removed from document flow and does not appear in page content
  • It renders in the margin box on every page
  • A later element with the same running name replaces it from that page onward

Use that replacement behavior for chapter-specific headers:

CSS
.chapter-header { position: running(chapterHead); }

@page {
  @top-left {
    content: element(chapterHead);
  }
}
HTML
<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>

string-set and running() compared:

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

Use string-set for text such as chapter titles. Use running() for logos, icons, or other styled HTML.


Fonts

For predictable output, use a font available to the renderer or embed it in the document.

Web-safe fonts

These web-safe fonts work without embedding:

CSS
body {
  font-family: Arial, Helvetica, sans-serif;
}

.monospace {
  font-family: "Courier New", Courier, monospace;
}

.serif {
  font-family: Georgia, "Times New Roman", serif;
}

Custom fonts

Embed fonts using @font-face:

CSS
@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 CDN

Link to Google Fonts directly in your HTML:

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:

CSS
@font-face {
  font-family: 'EmbeddedFont';
  src: url(data:font/woff2;base64,d09GMgABAAAAAA...) format('woff2');
}

Font rules

CSS
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

CSS
/* 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;
  }
}
CSS
@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;
  }
}
CSS
@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;
  }
}

Tables

Tables need explicit break and header rules when they span pages.

Basic table styling

CSS
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

Repeat table headers on every page:

HTML
<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>
CSS
thead {
  display: table-header-group;
  background: #f0f0f0;
}

th {
  font-weight: bold;
  text-align: left;
  padding: 8px;
  border-bottom: 2px solid #333;
}

Long tables

CSS
/* 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

CSS
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 borders

CSS
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

The PDF renderer does not execute JavaScript. Chart.js, D3.js, Plotly.js, Highcharts, and ApexCharts therefore produce no output, and <canvas> elements remain blank. Render charts as static SVG or images before adding them to the HTML.

Inline SVG

SVG is the recommended format because it stays sharp at any zoom level:

HTML
<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>

Embedded images

When SVG isn’t available, use base64-encoded or hosted images:

HTML
<!-- 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;" />

Chart styles

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

Plotly with Python

In a Python script, server, or code interpreter, Plotly can export publication-quality charts as static SVG without a browser:

Python
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:

HTML
<!-- 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>

Plotly requires the kaleido package for static export: pip install plotly kaleido

Other rendering options

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

Chart issues

  • A <canvas> stays blank because it requires JavaScript. Use SVG or <img> instead.
  • SVG <text> elements do not inherit document fonts. Set font-family and font-size explicitly.
  • Use viewBox for responsive SVG scaling instead of fixed pixel dimensions.
  • Wrap charts in a container with break-inside: avoid so they do not split across pages.
  • Use high-contrast print colors instead of transparency or subtle gradients.

PDF bookmarks

PDF bookmarks create a clickable outline in the viewer sidebar. Use -pdfmcp-bookmark-* properties to build it from document headings:

CSS
/* 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, 1-6Depth 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 an element by its id in a “see page X” reference or table of contents:

CSS
a.page-ref::after {
  content: " (page " target-counter(attr(href), page) ")";
}
HTML
<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:

CSS
.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;
  }
}
HTML
<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:

CSS
body {
  hyphens: auto;
  -webkit-hyphens: auto;
}

Requirement: Set the lang attribute on <html> for the hyphenation dictionary to load:

HTML
<html lang="en">

Combine it with text-align: justify to reduce uneven spacing in justified paragraphs.


Preserve backgrounds

Background colors are stripped by default in print rendering. Force them to appear with print-color-adjust:

CSS
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 across pages

When an element with borders or backgrounds splits across a page boundary, box-decoration-break: clone repeats the full decoration on each fragment:

CSS
.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

Combine leader() and target-counter() to create classic TOC entries with dot fills:

CSS
.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; }
HTML
<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:

Plain text
Introduction ............ 1
  Background ............ 3
Methods ................. 5

You can use different fill characters: leader("."), leader("-"), leader("_"), or leader(" ").


CSS support

Paged PDF rendering supports a subset of CSS:

FeatureSupportNotes
CSS GridSupported for simple casesTest complex grids and page breaks
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 ShadowNot supportedUse borders or background colors instead
Border RadiusSupportedWorks normally
OpacitySupportedWorks normally
AnimationsNot supportedStatic rendering only
JavaScriptNot executedNo <script>, no dynamic rendering
position: fixedSupportedFixed elements repeat across pages
position: stickyNot supportedUse thead { display: table-header-group; }
Viewport unitsNot supportedNo vw/vh; use mm, cm, in, pt

Use block and inline layout, floats, tables, or basic flexbox. Use CSS Paged Media properties such as @page, break-*, and margin boxes for print behavior.


Invoice example

This invoice combines the page, font, table, and print rules from the guide:

HTML
<!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>

Rendering notes

General

  1. Preview PDF output throughout development.
  2. Use pt for font sizes and cm or in for margins.
  3. Keep layouts simple enough to paginate predictably.
  4. Include fallback fonts and graceful degradation.

Performance

  • Inline critical CSS to avoid external stylesheet requests.
  • Size images for their intended output; use base64 for small graphics.
  • Limit external resources because each one adds latency.

Accessibility

  • Use enough contrast for text to remain readable in print.
  • Keep body text at 10pt or larger.
  • Use semantic HTML to preserve meaningful content flow.

Common issues

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

CSS properties

CSS
/* 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;

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