Why your HTML to PDF looks broken, and how to fix each problem
When HTML to PDF is not rendering correctly, the cause is almost always timing or print rules: fonts and charts captured before they finish loading, 100vh sections sized to the page, fixed headers repeated, backgrounds dropped by the print engine, or page breaks falling through content. Each has a direct fix in CSS (print-color-adjust, break-inside, @media print) or in the converter settings.
First, check which kind of PDF you are making
There are two very different ways to turn a web page into a PDF, and they fail in different ways. Print / Save as PDF uses the browser's print engine: text stays selectable, but print rules apply, so backgrounds can disappear, fixed headers repeat and page breaks land where the engine decides. A screenshot-based PDF (an html2canvas or html-to-image capture saved with jsPDF, like the Download PDF button in our free HTML to PDF converter) copies what is on screen pixel by pixel, but the text is not selectable and images from other websites can go missing.
So before fixing anything, note which method produced the broken file. If the problem only appears in one of them, switching to the other is sometimes the fastest fix. The converter offers both from the same preview, which makes the comparison quick. For the background on both methods, see the three free ways to convert HTML to PDF.
Troubleshooting table: symptom, cause and fix
| Symptom | Likely cause | Fix |
|---|---|---|
| Wrong font (Arial, Times) | Web font not loaded at capture, or font file not reachable | Link tag in the head, font files in the zip, wait 1-2 s |
| Blank or half-drawn charts | Chart animation still running, canvas sized for the screen | animation: false, extra wait, resize on beforeprint |
| Hero fills a whole page | 100vh or h-screen sections | height: auto in print, or pin 100vh to the viewport height |
| Header on every page | position: fixed is repeated by the print engine | position: static in print, or show fixed headers once |
| Dark background turned white | Browsers drop backgrounds when printing | print-color-adjust: exact and Background graphics |
| Lines or images cut in half | A page break falls inside an element | break-inside: avoid, smart page breaks |
| Images missing (lazy) | loading="lazy" images never scrolled into view | loading="eager", or let the converter scroll the page |
| Images missing only in screenshot PDF | Cross-origin images without CORS headers | Use Print, or put the images in the zip |
| Empty sections | Scroll-reveal or entrance animations start at opacity 0 | Freeze animations, make visible the default style |
| Unstyled page | Tailwind Play CDN has not generated the CSS yet | Wait 2-3 seconds before capture |
The sections below explain each fix with code you can paste. Most of them also exist as toggles in the HTML to PDF tool, so you can often fix the output without touching the HTML.
Fonts: the PDF uses Arial or Times instead of your web font
Web fonts load after the HTML, so a PDF made too early shows the fallback font, with different line breaks and widths. Load Google Fonts with a link tag in the head (not an @import deep inside a stylesheet). For self-hosted fonts, use relative paths and convert a .zip that contains the HTML and the font files, so the paths resolve.
Our converter waits for document fonts before capturing; if text is set by a script after load, add a second or two of extra wait. Always give a fallback stack close in width so a missing font does not break the layout.
@font-face {
font-family: 'Brand Sans';
src: url('fonts/brand-sans.woff2') format('woff2');
font-weight: 400 700;
font-display: swap;
}
body { font-family: 'Brand Sans', system-ui, sans-serif; }
Charts are blank or half-drawn
Chart.js animates charts by default for about a second, and a canvas is drawn for the size it had on screen. Captured too early, the bars are missing or half-height; printed on a page of a different width, the canvas can come out stretched. Turn off the animation, give the chart container a fixed height, and resize charts before printing.
// The canvas sits in a container with a fixed height:
// <div style="position: relative; height: 360px"><canvas id="sales"></canvas></div>
new Chart(document.getElementById('sales'), {
type: 'bar',
data: {
labels: ['Q1', 'Q2', 'Q3'],
datasets: [{ label: 'Sales', data: [12, 19, 23] }]
},
options: { animation: false, responsive: true, maintainAspectRatio: false }
});
// Redraw every chart at the print size before the browser prints
window.addEventListener('beforeprint', () => {
Object.values(Chart.instances).forEach((chart) => chart.resize());
});- In the converter, add a few seconds of wait for pages that build charts from scripts.
- D3 transitions behave like animations: capture after they end, or skip the transition when the page is printed.
- Charts drawn in WebGL can come out blank in screenshot-based outputs.
Layout problems: 100vh sections and repeated fixed headers
100vh and h-screen sections
A hero section set to 100vh (h-screen in Tailwind) is as tall as the window. When printing, viewport units are resolved against the page area, so the hero takes a whole page and leaves a large gap. In a screenshot capture, it takes the height of whatever window the tool uses. Reset the height in print, or let the converter pin 100vh sections to the viewport height you chose.
@media print {
.hero, .h-screen { min-height: auto; height: auto; padding: 48px 0; }
}Fixed or sticky headers on every page
Chrome and Edge print position: fixed elements on every page, so a fixed navigation bar covers the top of each page. Sticky headers can end up in odd places too. Make them static in print and hide widgets that make no sense on paper. The converter can also show fixed and sticky headers once, at the top, instead of repeating them.
@media print {
header, .navbar { position: static; }
.cookie-banner, .chat-widget, .back-to-top { display: none; }
}
Dark background turned white
To save ink, browsers drop background colours and images when printing unless told otherwise. A dark-themed dashboard then becomes white text on a white page. Two fixes: tick Background graphics in the print dialog (More settings in Chrome and Edge), or declare in CSS that colours must be kept exactly. The property is inherited, so setting it on html covers the page.
html {
-webkit-print-color-adjust: exact; /* Safari and older Chromium */
print-color-adjust: exact;
}
Text, images and table rows cut at page breaks
The print engine breaks pages wherever the content reaches the bottom of the page area, including through the middle of a line of text, an image or a card. Tell it which elements must stay whole, where new pages should start, and how many lines may be left alone at the top or bottom of a page.
figure, img, tr, pre, blockquote, .card { break-inside: avoid; }
h2, h3 { break-after: avoid; }
.chapter { break-before: page; }
p { orphans: 3; widows: 3; }
thead { display: table-header-group; } /* repeat table headers on each page */- break-inside: avoid is well supported; it cannot help an element taller than a page, which will still be split.
- Breaks inside flex and grid containers are handled less consistently than in normal block layout; keep printable cards in a simple block flow where you can.
- orphans and widows work in Chromium and Safari but not in Firefox.
- break-after: avoid on headings is less consistently supported than break-inside, so test in the browser you print with.
- The screenshot-based Download PDF in our converter places smart breaks that avoid cutting through paragraphs, images and table rows where possible.
Missing images and invisible content
Lazy images and scroll-reveal content
Images with loading="lazy" are only fetched near the viewport, and libraries such as AOS keep elements at opacity 0 until you scroll to them. A capture of the whole page can therefore show empty frames and blank blocks. Use eager loading for images that matter, force the revealed state in print, or let the converter scroll through the page and force lazy images to load before capture.
<img src="images/team.jpg" alt="Team photo" loading="eager" width="1200" height="800">@media print {
[data-aos], .reveal-on-scroll { opacity: 1 !important; transform: none !important; }
}Images from other websites (CORS)
Screenshot-based outputs redraw the page on a canvas. Browsers block reading pixels from images on other domains unless that server sends CORS headers, so such images can be missing in Download PDF, PNG or PPTX while they appear fine in Print. Put the images in a .zip next to the HTML, host them on a server that sends Access-Control-Allow-Origin, or use Print / Save as PDF.
Entrance animations that never finish
If an element is hidden in its base style and only an animation makes it visible, anything that stops the animation (a print stylesheet, a capture at the wrong moment, reduced-motion settings) leaves it hidden. Freeze animations at their end state in the converter, and write animations so the visible state is the default.
/* Fragile: invisible unless the animation runs to the end */
.card { opacity: 0; animation: fade 0.5s forwards; }
@keyframes fade { to { opacity: 1; } }
/* Robust: visible by default, only the start is animated */
.card { animation: fade-in 0.5s both; }
@keyframes fade-in { from { opacity: 0; } }
A print stylesheet you can paste
This block combines the fixes above. Add it at the end of your CSS, adapt the selectors to your page, and use Print / Save as PDF. Note the last line: it switches off animations, so it is only safe once your animated elements are visible by default, as shown above.
@page { size: A4; margin: 12mm; }
@media print {
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
header, .navbar { position: static; }
nav, .cookie-banner, .chat-widget { display: none; }
.hero, .h-screen { min-height: auto; height: auto; }
h2, h3 { break-after: avoid; }
figure, img, tr, pre, .card { break-inside: avoid; }
thead { display: table-header-group; }
[data-aos], .reveal-on-scroll { opacity: 1 !important; transform: none !important; }
*, *::before, *::after { animation: none !important; transition: none !important; }
}- More rules (named pages, link URLs, margins) are in the print CSS cheat sheet.
- AI-generated pages? The ChatGPT canvas to PDF guide includes a prompt that asks the AI to add print CSS for you.
- Making slides rather than documents? Follow the rules for HTML slides that survive conversion.
FAQ
Why does my HTML look different in the PDF than in the browser?
Print / Save as PDF applies print rules: it drops backgrounds by default, repeats fixed elements and breaks pages through content. Screenshot-based PDFs copy the screen but can capture before fonts, charts or animations finish. Identify the method, then apply the matching fix.
How do I keep background colours when saving HTML as PDF?
Add print-color-adjust: exact (plus the -webkit- prefixed version for Safari) on the html element, or tick Background graphics in the print dialog. Screenshot-based PDFs keep backgrounds automatically because they copy what is on screen.
Why are my charts empty in the PDF?
The chart was still animating, or had not been drawn yet, when the PDF was captured. Set animation: false in Chart.js, wait a few seconds before capture, and resize charts on the beforeprint event so they fit the page width.
How do I stop a page break from cutting an image or a table row?
Apply break-inside: avoid to images, figures, cards and table rows, and display: table-header-group on thead so headers repeat. An element taller than a page will still be split, so keep such blocks shorter than one page.
Why is the text in my PDF not selectable?
The PDF was built from screenshots of the page (for example with html2canvas and jsPDF), so each page is an image. Use Print / Save as PDF instead: the browser print engine keeps real, selectable and searchable text.
Try it: free HTML to PDF converter
Runs in your browser: no upload, no sign-up, no watermark.
A free tool by Dawwerni, makers of gamified loyalty for local businesses.