HTML to PDF: background colors and images not printing

You print a page to PDF with Puppeteer, Playwright or headless Chrome, and the colored header, the table stripes and the badges are gone: plain black text on white. We built two test pages (a custom invoice and a Bootstrap 4 report) and printed them both with Chromium 156 in each configuration, then measured the pixels with poppler. Only four things cause this, and each has a one-line fix.

What we measured

SymptomCause and fix
Every background is gone: header, badges, gradients, page color (0% of the indigo header left)page.pdf() defaults to printBackground: false in Puppeteer and Playwright, like Chrome's unticked "Background graphics" box.
Fix: printBackground: true, or the CSS below
Backgrounds print, but a white frame runs round every pageThe margin passed to page.pdf() is never painted: page color coverage fell from 78% to 65% with 10mm margins.
Fix: @page { margin: 0 } and padding on the body
Bootstrap 4 tables print white, the navbar vanishes, text is 29% smallerBootstrap 4's own @media print block, even with printBackground: true.
Fix: print with screen CSS, or override 4 rules

1. Turn printBackground on

This is the cause nine times out of ten. Chrome treats backgrounds as "ink saving" material when printing and drops them unless asked. In Playwright (Node):

import { chromium } from 'playwright';
import { readFile } from 'node:fs/promises';

const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent(await readFile('report.html', 'utf8'), { waitUntil: 'networkidle' });
// await page.emulateMedia({ media: 'screen' }); // optional: skip @media print rules
await page.pdf({
  path: 'report.pdf',
  format: 'A4',
  printBackground: true, // default false: every background color and image is dropped
});
await browser.close();

Puppeteer takes the same printBackground: true option in page.pdf(), and its screen-CSS call is page.emulateMediaType('screen'). Python's Playwright uses print_background=True.

2. Or fix it in CSS: print-color-adjust

If you can't change the renderer options (a third-party service, the browser's own print dialog, a CLI), put this in the page. With printBackground left at false, our test page then printed exactly like the true version (6.5% indigo header, 78% page color, same as with the option):

/* prints backgrounds even when printBackground / "Background graphics" is off */
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

Scope it to the elements that need it (.header, .badge) if you'd rather let other backgrounds drop. Older Chrome versions only read the -webkit- name, so keep both.

3. Full-bleed page color: the white frame

Margins set in page.pdf({ margin }) sit outside the page box, and Chrome never paints them, so a page background stops 10mm short of the paper edge. A CSS @page margin wins over the option (we checked with a 10mm option and @page { margin: 0 }: the color reached the corners). Move the white space inside the page instead:

@page { margin: 0; }          /* overrides the margin passed to page.pdf() */
html { background: #eef2ff; } /* now reaches the paper edge on every page */
body { margin: 0; padding: 15mm; }

The body padding only pads the top of page 1 and the bottom of the last page. If text on later pages runs too close to the edge, keep a small margin and accept the frame, or give each page its own padded section with a page break after it.

4. Bootstrap 4 (and other frameworks) strip colors in print

Bootstrap 4.6's CSS ends with an @media print block. With printBackground: true, our report still lost the dark navbar, the striped rows, the table-success row and the dark table head, and the text came out 29% smaller. The rules responsible, from bootstrap.min.css:

@media print {
  *,::after,::before{text-shadow:none!important;box-shadow:none!important}
  @page{size:a3}
  body{min-width:992px!important}
  .container{min-width:992px!important}
  .navbar{display:none}
  .table td,.table th{background-color:#fff!important}
  /* ...and more */
}

The @page{size:a3} is the sneaky one: Chrome lays the page out on A3 and scales it down onto your A4 sheet (unless you pass preferCSSPageSize: true, which gives you an A3 PDF instead). Bootstrap 5 dropped all of this and only keeps its .d-print-* display utilities, which is why the same markup prints fine after an upgrade. Two ways out:

@page { size: A4; } /* Bootstrap 4 sets size: a3, so Chrome shrinks everything onto A4 */
@media print {
  /* Bootstrap 4 paints every table cell white, hiding row colors and stripes */
  .table td, .table th { background-color: transparent !important; }
  .table .thead-dark th { background-color: #343a40 !important; color: #fff !important; }
  .table-success > td, .table-success > th { background-color: #c3e6cb !important; } /* one line per row color you use */
  /* it also hides the navbar and forces a 992px minimum layout width */
  .navbar { display: flex !important; }
  body, .container { min-width: 0 !important; }
}

Older themes built on Bootstrap 3 and the HTML5 Boilerplate print styles go further (Bootstrap 3 sets * { background: transparent !important; color: #000 !important }); for those, screen CSS is the only practical route. To find the culprit on any site, search its CSS for @media print and for !important near background.

With the Shotmint API

/v1/pdf has print_background on by default, so cause 1 can't happen. Pass "screen_media": true to print with screen CSS, and "margin": "0mm" for full-bleed pages. This prints the Bootstrap 4.6 tables documentation:

curl -s -X POST https://shotmint.creasework.com/v1/pdf \
  -H "X-API-Key: $SHOTMINT_KEY" -H "Content-Type: application/json" \
  -d '{"url": "https://getbootstrap.com/docs/4.6/content/tables/", "screen_media": true}' \
  -o tables.pdf

Run against the live API: 20 A4 pages, 1 credit, with the table-dark examples dark as on screen. Without screen_media the same call gives 19 pages of white tables in smaller text. (Writing this guide is how we found that our own screen_media option had stopped working for PDFs, because page.pdf() switches to print CSS unless screen media is set explicitly. It's fixed and re-tested above.)

Background checklist

Related: Tailwind print classes, CSS page breaks, page numbers and footers, custom fonts in PDFs.

What it costs

One PDF = one credit, however many pages it has. €5 buys 2,000 renders, credits never expire and there's no subscription.

Try it with 25 free renders a month

No signup, no card. Get a key in one click, then buy credits only if you need more (from €5 for 2,000, never expire). From a script, an AI agent, Make or Zapier, POST /v1/keys/free (no body) returns a key as JSON (openapi.json, no-code recipes).

Get a free API key Full API reference

More guides: HTML to PDF and HTML to image

All guides: guides · integrations · comparisons