Printing from a web part gives a blank page when the print CSS hides body > * and the print layer lives inside the web part
Hiding the whole SharePoint page in print with body > * { display: none !important } and showing only your print layer works only if the print layer is itself a direct child of <body>. Rendered anywhere inside the web part, it sits under a hidden ancestor and the printout is empty. Render it with ReactDOM.createPortal(…, document.body) and route every print through that one component.
Skrýt v tisku celou stránku SharePointu přes body > * { display: none !important } a ukázat jen tiskovou vrstvu funguje jen tehdy, když je ta vrstva sama přímým potomkem <body>. Vykreslená kdekoli uvnitř webpartu leží pod skrytým předkem a tisk vyjde prázdný. Vykresli ji přes ReactDOM.createPortal(…, document.body) a veď každý tisk přes tuhle jedinou komponentu.
Symptom
The web part has a "Print / Save as PDF" button. The report renders fine on screen (or is prepared off screen), the browser's print dialog opens, and the preview is a blank page. Nothing fails in the console. A second variant: a button that calls window.print() directly prints an empty page too, because the same stylesheet hides the whole page and nothing is marked as the print layer.
Cause
The print stylesheet usually looks like this:
.app-print { display: none; }
@media print {
body > * { display: none !important; } /* suite bar, navigation, footer, the page itself */
.app-print { display: block !important; }
}
On a SharePoint page, the web part is deep inside the page's own root element, which is one of the body > * elements hidden in print. An element whose ancestor has display: none is never rendered, whatever its own display says. So a print layer rendered next to the report component, inside the web part, disappears together with the page.
Neither the screen nor a unit test without the print media type shows the problem: on screen the layer is hidden on purpose.
Fix
Render the print layer as a direct child of <body> and make it the only way the web part prints:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
export const PrintLayer: React.FC<{ children: React.ReactNode }> = ({ children }) =>
ReactDOM.createPortal(<div className="app-print">{children}</div>, document.body);
export function usePrint(): { printing: boolean; print: () => void } {
const [printing, setPrinting] = React.useState(false);
const print = React.useCallback(() => {
setPrinting(true); // render the layer first…
window.setTimeout(() => { // …then open the print dialog
try { window.print(); } finally { setPrinting(false); }
}, 60);
}, []);
return { printing, print };
}
The specificity works out: .app-print (0,1,0) beats body > * (0,0,1) when both are !important.
Notes
- Keep a small test that renders the layer inside a nested component and asserts it ends up in
document.body.children, plus a source scan that fails when the print class orwindow.print(appears anywhere else. It is the cheapest check that catches a regression — print previews rarely get looked at during development. - CSS custom properties (theme tokens) set on the web part's root do not reach a portal in
<body>. Put them on<html>or on a class you also give the portal.