Why your HTML to PDF looks broken, and how to fix each problem

Updated: · Dawwerni team

Title card about fixing an HTML page that renders badly when converted to PDF

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

SymptomLikely causeFix
Wrong font (Arial, Times)Web font not loaded at capture, or font file not reachableLink tag in the head, font files in the zip, wait 1-2 s
Blank or half-drawn chartsChart animation still running, canvas sized for the screenanimation: false, extra wait, resize on beforeprint
Hero fills a whole page100vh or h-screen sectionsheight: auto in print, or pin 100vh to the viewport height
Header on every pageposition: fixed is repeated by the print engineposition: static in print, or show fixed headers once
Dark background turned whiteBrowsers drop backgrounds when printingprint-color-adjust: exact and Background graphics
Lines or images cut in halfA page break falls inside an elementbreak-inside: avoid, smart page breaks
Images missing (lazy)loading="lazy" images never scrolled into viewloading="eager", or let the converter scroll the page
Images missing only in screenshot PDFCross-origin images without CORS headersUse Print, or put the images in the zip
Empty sectionsScroll-reveal or entrance animations start at opacity 0Freeze animations, make visible the default style
Unstyled pageTailwind Play CDN has not generated the CSS yetWait 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());
});

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 */

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; }
}

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.