pdf-mcp Documentation
Browse documentation
Markdown

CSS print styling

CSS rules for PDF page size, breaks, headers, footers, counters, and print layout

On this page

This guide covers the CSS used to control PDF pages, including size, margins, breaks, running content, counters, and print layout.

@page rules

The @page rule controls page size, margins, and other page-level properties.

Page size

Set the page dimensions using the size property:

CSS
@page {
  size: A4;              /* Standard paper sizes */
}

@page {
  size: Letter;          /* US Letter (8.5in x 11in) */
}

@page {
  size: 210mm 297mm;     /* Explicit dimensions (width height) */
}

@page {
  size: A4 landscape;    /* Orientation modifier */
}

Common paper sizes:

SizeDimensionsCommon use
A4210mm x 297mmInternational standard
Letter8.5in x 11inUS standard
Legal8.5in x 14inLegal documents
A3297mm x 420mmLarge format
A5148mm x 210mmBooklets

Page margins

Control the printable area with margins:

CSS
@page {
  size: A4;
  margin: 2cm;           /* All sides */
}

@page {
  margin: 2cm 1.5cm;     /* Top/bottom, left/right */
}

@page {
  margin: 2cm 1.5cm 3cm 1.5cm;  /* Top, right, bottom, left */
}

@page {
  margin-top: 2.5cm;
  margin-right: 2cm;
  margin-bottom: 3cm;
  margin-left: 2cm;
}

Named pages

Target specific pages with named page rules for different layouts within a single document:

CSS
/* Define different page styles */
@page cover {
  margin: 0;
  size: A4;
}

@page content {
  margin: 2cm;
  size: A4;
}

@page landscape-chart {
  size: A4 landscape;
  margin: 1.5cm;
}

/* Apply to elements */
.cover-page {
  page: cover;
}

.content-section {
  page: content;
}

.chart-page {
  page: landscape-chart;
}

Left and right pages

Style left and right pages differently for book-style layouts:

CSS
@page :left {
  margin-left: 3cm;
  margin-right: 2cm;
}

@page :right {
  margin-left: 2cm;
  margin-right: 3cm;
}

First page

Apply special styling to the first page:

CSS
@page :first {
  margin-top: 5cm;       /* Extra space for title */
}

/* Or combined with named pages */
@page report:first {
  @top-center {
    content: none;       /* No header on first page */
  }
}

Page breaks

Force page breaks

CSS
/* Force a break before an element */
.chapter {
  break-before: page;
  page-break-before: always;    /* Legacy syntax */
}

/* Force a break after an element */
.section-end {
  break-after: page;
  page-break-after: always;
}

Prevent page breaks

CSS
/* Never break inside this element */
.keep-together {
  break-inside: avoid;
  page-break-inside: avoid;
}

/* Prevent breaks between heading and following content */
h1, h2, h3 {
  break-after: avoid;
  page-break-after: avoid;
}

Break values

ValueDescription
autoDefault behavior
avoidPrevent break if possible
alwaysAlways insert a page break
pageForce a page break
leftBreak and go to next left page
rightBreak and go to next right page

Example

CSS
/* Document structure */
.chapter {
  break-before: page;
}

h1 {
  break-after: avoid;       /* Keep with following content */
}

table {
  break-inside: avoid;      /* Don't split tables */
}

tr {
  break-inside: avoid;      /* Don't split table rows */
}

figure {
  break-inside: avoid;      /* Keep images with captions */
}

blockquote {
  break-inside: avoid;
}

Headers and footers

CSS page margin boxes allow you to place content in the margins of every page.

Margin box positions

The page margin is divided into 16 boxes:

Plain text
┌──────────────────────────────────────────┐
│  @top-left-corner  @top-left  @top-center  @top-right  @top-right-corner  │
├──────────────────────────────────────────┤
│  @left-top         │                     │  @right-top      │
│  @left-middle      │    PAGE CONTENT     │  @right-middle   │
│  @left-bottom      │                     │  @right-bottom   │
├──────────────────────────────────────────┤
│  @bottom-left-corner  @bottom-left  @bottom-center  @bottom-right  @bottom-right-corner  │
└──────────────────────────────────────────┘

Add headers

CSS
@page {
  @top-center {
    content: "Document Title";
    font-size: 10pt;
    color: #666;
  }
}

@page {
  @top-left {
    content: "Chapter Name";
  }
  @top-right {
    content: "Section Name";
  }
}

Add footers

CSS
@page {
  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
    font-size: 9pt;
  }
}

@page {
  @bottom-left {
    content: "Confidential";
    font-size: 8pt;
    color: #999;
  }
  @bottom-right {
    content: "© 2024 Company Name";
    font-size: 8pt;
  }
}

Style margin boxes

CSS
@page {
  @top-center {
    content: "Report Title";
    font-family: Arial, sans-serif;
    font-size: 11pt;
    font-weight: bold;
    color: #333;
    border-bottom: 1px solid #ccc;
    padding-bottom: 5pt;
  }

  @bottom-center {
    content: counter(page);
    font-size: 10pt;
    padding-top: 5pt;
    border-top: 0.5pt solid #ccc;
  }
}

Running elements

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

CSS
/* Remove the element from the flow and register it */
.running-header {
  position: running(headerContent);
}

/* Place it in a margin box */
@page {
  margin-top: 3cm;

  @top-center {
    content: element(headerContent);
  }
}
HTML
<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>

<p>Document content...</p>

Behavior:

  • The element is removed from the normal document flow (it won’t appear inline)
  • It renders in the margin box on every page
  • If a new element with the same running name appears later in the document, it replaces the previous one from that page onward

Replacing the running element lets a document change headers by chapter:

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

@page { @top-left { content: element(chapterHead); } }
HTML
<section>
  <div class="chapter-header">Chapter 1: Introduction</div>
  <p>Content...</p>
</section>

<section class="chapter">
  <div class="chapter-header">Chapter 2: Analysis</div>
  <p>Content...</p>
</section>

Choose an approach:

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

Page counters

Use CSS counters to display page numbers and total page counts.

Basic page numbers

CSS
@page {
  @bottom-center {
    content: counter(page);
  }
}

Page X of Y

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

Roman numerals and letters

CSS
/* Front matter with roman numerals */
@page frontmatter {
  @bottom-center {
    content: counter(page, lower-roman);   /* i, ii, iii, iv... */
  }
}

/* Alternative formats */
content: counter(page, upper-roman);       /* I, II, III, IV... */
content: counter(page, lower-alpha);       /* a, b, c, d... */
content: counter(page, upper-alpha);       /* A, B, C, D... */

Custom counter styles

CSS
/* Reset page counter for main content */
.main-content {
  counter-reset: page 1;
}

/* Section-based page numbers */
@page {
  @bottom-center {
    content: "Section " counter(section) " - Page " counter(page);
  }
}

body {
  counter-reset: section;
}

.section {
  counter-increment: section;
}

Widows and orphans

Prevent single lines from appearing alone at the top or bottom of pages.

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

/* Stricter control for important content */
.important-paragraph {
  widows: 4;
  orphans: 4;
}

Definitions:

  • Widows: Lines left alone at the top of a new page
  • Orphans: Lines left alone at the bottom of a page before a break

@media print

Use the print media query to apply styles only when generating PDFs or printing.

Basic usage

CSS
/* Screen-only styles */
@media screen {
  .nav-menu {
    display: flex;
  }
}

/* Print-only styles */
@media print {
  .nav-menu {
    display: none;
  }

  body {
    font-size: 12pt;
    line-height: 1.4;
  }
}

Hide screen elements

CSS
@media print {
  /* Hide navigation and interactive elements */
  nav,
  .sidebar,
  .social-share,
  .comments,
  .footer-links,
  button:not(.print-include),
  input,
  .no-print {
    display: none !important;
  }

  /* Remove backgrounds for cleaner print */
  * {
    background: transparent !important;
  }
}
CSS
@media print {
  /* Show URLs after links */
  a[href^="http"]:after {
    content: " (" attr(href) ")";
    font-size: 0.9em;
    color: #666;
  }

  /* But not for internal links */
  a[href^="#"]:after,
  a[href^="/"]:after {
    content: none;
  }
}

Invoice or report

CSS
@page {
  size: A4;
  margin: 2cm 1.5cm 3cm 1.5cm;

  @top-right {
    content: "Invoice #" attr(data-invoice-number);
    font-size: 10pt;
  }

  @bottom-center {
    content: "Page " counter(page) " of " counter(pages);
    font-size: 9pt;
    color: #666;
  }
}

.invoice-header {
  break-after: avoid;
}

.line-items {
  break-inside: avoid;
}

.line-items tr {
  break-inside: avoid;
}

.invoice-total {
  break-before: avoid;
  break-inside: avoid;
}

Book or long document

CSS
@page {
  size: 6in 9in;          /* Trade paperback size */
  margin: 0.75in 0.5in;
}

@page :left {
  margin-left: 0.75in;
  margin-right: 0.5in;

  @bottom-left {
    content: counter(page);
  }
}

@page :right {
  margin-left: 0.5in;
  margin-right: 0.75in;

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

@page :first {
  @bottom-left {
    content: none;        /* No page number on title page */
  }
}

.chapter {
  break-before: right;    /* Chapters start on right pages */
}

h1 {
  break-after: avoid;
}

img {
  max-width: 100%;
  break-inside: avoid;
}

Multi-column layout

CSS
.content {
  columns: 2;
  column-gap: 1.5em;
  column-rule: 1px solid #ddd;
}

/* Prevent column breaks inside elements */
.content p {
  break-inside: avoid-column;
}

.content h2 {
  column-span: all;       /* Headings span all columns */
  break-after: avoid;
}

Charts

The PDF renderer does not execute JavaScript. Chart.js, D3.js, Plotly.js, Highcharts, and ApexCharts therefore do not render, and <canvas> elements remain blank. Render charts as SVG or images before embedding them in HTML.

Inline SVG

SVG is the recommended chart format because it scales without losing sharpness and can be styled with CSS:

HTML
<div class="chart">
  <svg viewBox="0 0 400 220" xmlns="http://www.w3.org/2000/svg">
    <rect x="20" y="120" width="40" height="80" fill="#3498db"/>
    <rect x="80" y="80" width="40" height="120" fill="#3498db"/>
    <rect x="140" y="40" width="40" height="160" fill="#3498db"/>
    <rect x="200" y="60" width="40" height="140" fill="#3498db"/>
    <text x="40" y="215" text-anchor="middle" font-size="10">Q1</text>
    <text x="100" y="215" text-anchor="middle" font-size="10">Q2</text>
    <text x="160" y="215" text-anchor="middle" font-size="10">Q3</text>
    <text x="220" y="215" text-anchor="middle" font-size="10">Q4</text>
    <line x1="10" y1="200" x2="260" y2="200" stroke="#333" stroke-width="1"/>
    <line x1="10" y1="0" x2="10" y2="200" stroke="#333" stroke-width="1"/>
  </svg>
</div>

Embedded images

Use base64-encoded or hosted chart images:

HTML
<!-- Base64 -->
<img src="data:image/png;base64,iVBORw0KGgo..." alt="Chart" style="width:100%; max-width:600px;" />

<!-- Hosted URL -->
<img src="https://example.com/charts/revenue.png" alt="Chart" style="width:100%; max-width:600px;" />

Chart CSS

CSS
.chart {
  break-inside: avoid;
  page-break-inside: avoid;
  margin: 1cm 0;
  text-align: center;
}

.chart svg {
  max-width: 100%;
  height: auto;
}

.chart img {
  max-width: 100%;
  height: auto;
}

/* SVG text doesn't inherit document styles */
.chart svg text {
  font-family: Arial, sans-serif;
  font-size: 10pt;
  fill: #333;
}

Plotly with Python

With Python, Plotly can export charts as SVG or PNG without a browser:

Python
import plotly.graph_objects as go
import plotly.io as pio
import base64

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 SVG — embed directly in HTML
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:

HTML
<!-- Inline SVG (preferred) -->
<div class="chart">{svg_str}</div>

<!-- Or base64 image -->
<div class="chart">
  <img src="data:image/png;base64,{png_b64}" alt="Revenue" />
</div>

Requires pip install plotly kaleido

Other rendering options

ApproachHow
Server-side SVGGenerate SVG with D3 (Node.js) or matplotlib (Python) and embed inline
Export from JS libraryUse Chart.js .toBase64Image(), Plotly .toImage(), or D3 .node().outerHTML to export before sending HTML
Static image serviceUse QuickChart.io or similar to generate chart images from a URL
matplotlib / seabornplt.savefig(buf, format='svg') and embed the SVG string

Chart issues

IssueSolution
Canvas is blank<canvas> requires JS; use SVG or images instead
Chart overflows pageSet max-width: 100% and use viewBox on SVGs
SVG text looks wrongSet font-family and font-size explicitly on SVG <text> elements
Chart splits across pagesWrap in a container with break-inside: avoid
Colors look washed outUse print-friendly colors with high contrast; avoid transparency

PDF bookmarks

PDF bookmarks create a navigable outline in the PDF viewer sidebar. Use the -pdfmcp-bookmark-* properties to control the document outline:

CSS
/* Assign heading levels to the bookmark tree */
h1 { -pdfmcp-bookmark-level: 1; }
h2 { -pdfmcp-bookmark-level: 2; }
h3 { -pdfmcp-bookmark-level: 3; }

/* Custom bookmark labels */
h1 { -pdfmcp-bookmark-label: content(text); }

/* Collapse sub-levels by default */
h2 { -pdfmcp-bookmark-state: closed; }

Properties

PropertyValuesDescription
-pdfmcp-bookmark-levelnone, 1-6Heading level in the PDF outline
-pdfmcp-bookmark-labelcontent(text), content(before), content(), custom stringBookmark display text
-pdfmcp-bookmark-stateopen, closedWhether children are expanded by default

Example

CSS
/* Full document outline */
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 for appendix */
.appendix h2 {
  -pdfmcp-bookmark-level: 1;
  -pdfmcp-bookmark-label: "Appendix: " content(text);
}

/* Exclude from bookmarks */
.no-bookmark { -pdfmcp-bookmark-level: none; }

Cross-references

Use target-counter() to insert the page number of an element by its id, including in tables of contents and “see page X” references:

CSS
/* "See page 5" style references */
a.page-ref::after {
  content: " (page " target-counter(attr(href), page) ")";
}
HTML
<p>For details, see <a class="page-ref" href="#chapter3">Chapter 3</a>.</p>
<!-- Renders: "For details, see Chapter 3 (page 5)." -->

Combine it with leaders for a full table of contents. See Table of contents.


Footnotes

CSS footnotes automatically move content to the bottom of the page with proper numbering:

CSS
/* Move footnote content to page bottom */
.footnote {
  float: footnote;
}

/* Style the in-text reference number */
::footnote-call {
  content: counter(footnote);
  font-size: 0.7em;
  vertical-align: super;
  line-height: none;
}

/* Style the footnote marker at page bottom */
::footnote-marker {
  content: counter(footnote) ". ";
  font-weight: bold;
}

/* Optional: horizontal rule above footnotes */
@page {
  @footnote {
    border-top: 1px solid #ccc;
    padding-top: 0.5em;
  }
}
HTML
<p>This is a statement that needs a source.<span class="footnote">Smith et al., 2024, Journal of Examples.</span></p>

Hyphenation

Enable automatic hyphenation to improve text justification and reduce ragged edges:

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

Important: The lang attribute must be set on the <html> element for hyphenation to work:

HTML
<html lang="en">
PropertyValuesDescription
hyphensnone, manual, autoHyphenation mode
overflow-wrapnormal, break-wordBreak long words that overflow

Preserve backgrounds

By default, browsers strip background colors for printing. Force backgrounds to appear:

CSS
body {
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

/* Or on specific elements */
.colored-header {
  background: #2c3e50;
  color: white;
  print-color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

Box decoration across pages

Control how borders, padding, and backgrounds behave when an element splits across pages:

CSS
.callout-box {
  box-decoration-break: clone;    /* Repeat decoration on each fragment */
  -webkit-box-decoration-break: clone;
  border: 2px solid #3498db;
  padding: 1em;
  background: #f0f8ff;
}
ValueBehavior
slice (default)Decoration is sliced at the break; the top fragment has no bottom border and the bottom fragment has no top border
cloneEach fragment repeats the borders, padding, and background

Table of contents with leaders

Use leader() with target-counter() to create classic TOC entries with dot leaders:

CSS
.toc a {
  text-decoration: none;
  color: inherit;
  display: block;
}

.toc a::after {
  content: leader(".") target-counter(attr(href), page);
}

/* Indent sub-levels */
.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>
<!-- Renders as:
Introduction ............ 1
  Background ............ 3
Methods ................. 5
-->

Leader styles

StyleSyntaxResult
Dotsleader(".")Introduction ............ 1
Dashesleader("-")Introduction ------------ 1
Underscoresleader("_")Introduction ____________ 1
Spacesleader(" ")Introduction 1

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 both work
calc()SupportedWorks in all contexts
2D TransformsSupportedrotate(), scale(), translate(); no 3D
3D TransformsNot supportedNo perspective, rotateX/Y/Z, etc.
Box ShadowNot supportedUse borders or background colors instead
Border RadiusSupportedWorks normally
OpacitySupportedWorks normally
Animations / TransitionsNot supportedStatic rendering only
JavaScriptNot executedNo <script>, no dynamic rendering
position: fixedSupportedFixed elements repeat across pages
position: stickyNot supportedUse thead { display: table-header-group; } for sticky headers
Viewport unitsNot supportedvw, vh, vmin, vmax don’t work; use mm, cm, in, pt

Rendering notes

Typography

  • Use pt (points) for font sizes in print: font-size: 11pt;
  • Set appropriate line height: line-height: 1.4 to 1.6
  • Use print-friendly fonts: Georgia, Times New Roman, or embed custom fonts
  • Consider increasing font size slightly for better readability

Images and graphics

CSS
img {
  max-width: 100%;
  height: auto;
  break-inside: avoid;
}

figure {
  break-inside: avoid;
  margin: 1em 0;
}

figcaption {
  font-size: 0.9em;
  text-align: center;
}

Tables

CSS
table {
  width: 100%;
  border-collapse: collapse;
  break-inside: auto;
}

thead {
  display: table-header-group;    /* Repeat on each page */
}

tr {
  break-inside: avoid;
}

th, td {
  border: 0.5pt solid #333;
  padding: 6pt 8pt;
}

Colors

  • Use enough contrast for printed text and graphics
  • Avoid light colors that may not reproduce well
  • Use color-adjust: exact to preserve background colors:
CSS
.colored-box {
  background-color: #f0f0f0;
  color-adjust: exact;
  -webkit-print-color-adjust: exact;
}

Performance

  • Inline critical CSS to avoid external requests
  • Use base64-encoded images for small graphics
  • Minimize CSS complexity for faster rendering
  • Test with multi-page documents to verify pagination

Common patterns

Prevent content splits

CSS
/* Keep headings with following content */
h1, h2, h3, h4, h5, h6 {
  break-after: avoid;
}

/* Keep code blocks together */
pre, code {
  break-inside: avoid;
}

/* Keep list items together */
li {
  break-inside: avoid;
}

/* Keep figures together */
figure, .figure {
  break-inside: avoid;
}

Section separators

CSS
.chapter {
  break-before: page;
}

.appendix {
  break-before: page;
}

.section {
  break-before: auto;
  margin-top: 2em;
}

Full-bleed pages

CSS
@page full-bleed {
  margin: 0;
}

.full-page-image {
  page: full-bleed;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

Renderer compatibility

For predictable output across renderers:

CSS
/* Use both modern and legacy syntax */
.keep-together {
  break-inside: avoid;
  page-break-inside: avoid;  /* Legacy */
}

.new-page {
  break-before: page;
  page-break-before: always;  /* Legacy */
}

Test your styles

  1. Start with a simple document and add complexity gradually
  2. Test with multi-page content to verify pagination
  3. Check page breaks at different content lengths
  4. Verify headers/footers appear correctly on all pages
  5. Test named pages for different layout sections