Background colours missing from a PDF
The coloured header came out white, and the white text that sat on it is still there in a pale grey. That second half is the part worth noticing: nothing was removed. Chromium repainted the block and then recoloured the words on it so they would still be legible, which is why this reads as a washed-out document rather than a missing background.
printBackground: false, from the page's content stream
1 1 1 rg -> 0 0 795 69 re f the header rectangle, white
.6706 .6706 .6706 rg -> BT /F4 14 Tf the text, greyBoth objects are present, both are painted, and the colours are the ones an ink-saving printer would choose. With backgrounds on, the same two objects come out as written:
printBackground: true, the same two objects
.3098 .2745 .898 rg -> 0 0 795 69 re f the header rectangle, indigo
1 1 1 rg -> BT /F4 14 Tf the text, whiteMeasured on Chromium 151.0.7922.34 by reading the fill operators out of each PDF rather than by looking at a rasterised page, so "was it painted" has an exact answer.
Two settings, and the CSS wins
One of them lives in your renderer and one in the document, and the usual advice treats them as alternatives. They are not alternatives and they are not equal.
the same header, five renders
printBackground CSS header painted
on nothing yes
on print-color-adjust: exact yes
on print-color-adjust: economy yes
off nothing NO
off print-color-adjust: exact yesRead the last row. The flag is off, the header is painted anyway, and the property in the stylesheet is the reason. An element that asks for exact colour is opted out of the adjustment entirely, whatever the renderer was told, so a document can be immune to a setting its author never sees.
That is worth knowing in both directions. If your backgrounds print when you wanted them not to, the flag is not where to look.
The value that sounds like the off switch does nothing
print-color-adjust: economy is the other half of the property and it reads like the way to ask for the pale version. Row three of the table above is that value with backgrounds on, and the header is painted exactly as it is without it.
It is the default rather than an instruction, so writing it out changes nothing. The renderer’s flag is the only thing that turns the adjustment on.
The prefix everybody still writes is not needed
Almost every answer to this question gives -webkit-print-color-adjust, often with the unprefixed spelling beside it as an afterthought. Each one on its own, with backgrounds off, so the property is the only thing that can save the header:
printBackground: false, and only the CSS differs
-webkit-print-color-adjust: exact header survives
print-color-adjust: exact header survives
neither header droppedBoth work. Writing the prefixed form costs nothing and it is not doing anything a modern Chromium needs, so a stylesheet that carries only the standard spelling is not the reason your header is white.
What to put in the document
Name the elements whose colour carries meaning rather than reaching for the whole page. A status badge, a chart, a header band that identifies the document: those are the ones a reader loses something by seeing in grey.
.header,
.badge,
.chart {
print-color-adjust: exact;
}Applying it to bodyworks and takes every background with it, including the ones that were only ever decoration. On a colour printer that is somebody else’s ink.
Reading it out of your own PDF
Looking at the page tells you it is white. This tells you which of the two settings did it, which is the part you cannot see. Every fill colour in the file, in order:
node -e "
const {inflateSync}=require('zlib'),b=require('fs').readFileSync('invoice.pdf');
let i=0;while(1){const s=b.indexOf('stream',i);if(s<0)break;let t=s+6;
if(b[t]===13)t++;if(b[t]===10)t++;const e=b.indexOf('endstream',t);if(e<0)break;
try{for(const m of inflateSync(b.subarray(t,e)).toString('latin1')
.matchAll(/([0-9.]+) ([0-9.]+) ([0-9.]+) rg/g))console.log(m[0]);}catch{}
i=e+9}"A block that should be coloured and prints as 1 1 1 rg followed by a rectangle was adjusted rather than dropped, and grey text beside it confirms it: the adjustment moves both, and nothing else in a document produces that pair.
How we handle this
PaperPony renders PDFs from HTML through an API and turns backgrounds on by default, so an invoice with a coloured header arrives with the header. The setting is there to turn off, under Print Background Colours, for the documents somebody prints in volume.
The template guide covers the rest of the render options, and the invoice generator needs no account and runs the same renderer, which makes it the quickest way to see a coloured document come out of it.