Skip to content

HTML to PDF CSS not working: why your styles disappear and how to fix them

The page looks perfect in Chrome and the PDF comes out looking like 1996: no colors, no layout, everything stacked in one column. CSS loss is the single most reported HTML to PDF problem, and it has five distinct causes with five different fixes. Here is how to tell which one you have hit.

You open the page in Chrome and it looks exactly right. You run it through your HTML to PDF step and get back something that looks like a plain text document: no brand colors, no columns, headings that are barely bigger than body text, everything stacked vertically down the left edge. Nothing errored. The text is all there. The CSS just did not arrive.

This is the most reported HTML to PDF problem there is, and the reason it feels mysterious is that "CSS not working" is really five different problems wearing the same shirt. Each has a distinct cause and a distinct fix. Work through them in the order below and you will find yours quickly.

The short version

CSS goes missing in an HTML to PDF render for five reasons: the stylesheet is at a relative URL the renderer cannot resolve, a print media query is quietly changing the design, the rendering engine does not support the CSS you wrote (flexbox and grid are the usual casualties), the PDF was captured before the stylesheet finished loading, or the page uses modern color and layout syntax the engine is too old to parse. Check them in that order. The first two account for most cases, and the third explains almost every remaining one.

Reason one: the stylesheet URL is relative or unreachable

If you pass raw HTML to a renderer rather than a URL, every relative path in that HTML becomes a guess. A tag like <link rel="stylesheet" href="/css/app.css"> means "relative to the current document", and when the document is a string that was posted to an API, there is no current document. The renderer resolves the path against nothing, the request fails silently, and you get unstyled HTML.

The output is unmistakable once you know it: everything renders in Times New Roman, links are blue and underlined, and the layout is one long column. That is a browser rendering HTML with zero stylesheets, which is exactly what happened.

There are two clean fixes. The first is to use absolute HTTPS URLs for every stylesheet, font and image, so there is nothing to resolve. The second, and the more reliable one for generated documents, is to inline the CSS into the HTML you send.

<!-- fragile: relative, unresolvable when HTML is posted as a string -->
<link rel="stylesheet" href="/css/invoice.css">

<!-- better: absolute and public -->
<link rel="stylesheet" href="https://app.example.com/css/invoice.css">

<!-- best for generated documents: no network fetch at all -->
<style>
  body { font-family: Inter, sans-serif; color: #0f172a; }
  .invoice-total { font-weight: 700; font-size: 20px; }
</style>

Inlining sounds crude, but for documents it is the right call. It removes an entire class of failure, makes the render deterministic, and means the same HTML produces the same PDF whether it runs in staging, production or a colleague's laptop. Most template engines can inline a compiled stylesheet at render time with a couple of lines.

The same reasoning applies if the stylesheet is technically absolute but not publicly reachable: behind a login, on an internal network, or on a staging host the renderer cannot see. Open the stylesheet URL in a private browser window with no session. If it does not load there, it will not load for the renderer either.

Reason two: print media rules are changing your design

This one catches experienced developers, because the CSS is loading perfectly and still producing the wrong result. PDF generation is printing. A headless browser renders the page in print media, not screen media, which means every @media print rule in your stylesheet applies and every @media screen rule does not.

If your application has a print stylesheet, and most mature applications picked one up at some point, it is now in charge of your PDF. Common print rules hide navigation, strip backgrounds, force black text, collapse sidebars and set everything to a serif font "for readability". All perfectly sensible for a user hitting Ctrl+P, and all quietly wrecking your carefully designed document.

Diagnosing it takes about ten seconds. Open the page in Chrome, open DevTools, press Ctrl+Shift+P, type "rendering", choose "Show Rendering", and set "Emulate CSS media type" to print. What you see is what your PDF will look like. If it suddenly matches your broken output, you have found the cause.

The fix depends on intent. If the print stylesheet is meant for users printing pages and should not touch generated documents, scope your document CSS so it wins, or render documents from a dedicated layout that does not include the print stylesheet at all. If you want the document to be styled for print, put the rules you actually want inside @media print and treat that as the real design. Either way, be deliberate: the failure mode is having a print stylesheet nobody has looked at in three years silently owning your invoices.

Reason three: the engine does not support the CSS you wrote

If the CSS is loading and print media is not the culprit, the next question is what is actually doing the rendering, because HTML to PDF engines differ enormously in CSS coverage. This is where a lot of confusing behavior comes from: the CSS is fine, the engine simply does not implement it.

EngineCSS supportWhat typically breaks
wkhtmltopdf (archived 2023)Old WebKit fork, roughly 2012-eraFlexbox, grid, modern color syntax, most CSS variables
dompdf (PHP)CSS 2.1 with fragments of CSS 3Flexbox, grid, transforms, many pseudo-selectors
Flying Saucer, OpenHTMLtoPDF (JVM)CSS 2.1 plus some CSS 3 and paged mediaFlexbox, grid; requires well-formed XHTML
WeasyPrint (Python)Strong CSS including paged media, flexboxNo JavaScript engine, so scripted styling never runs
Headless ChromiumEverything a current Chrome supportsLittle, since it is the same engine as your browser

The tell for this cause is that your layout collapses into a single column while colors and fonts survive. That is a flexbox or grid container being ignored: the engine does not recognize display: flex, so children fall back to normal block flow and stack. No error, just a different layout.

You have two ways out. Either write your document CSS to the engine's actual capability level, which in practice means table-based layouts and floats instead of flexbox, or switch to an engine that runs the CSS you already have. Rewriting a modern template into table layout is real work and it means maintaining a second design that drifts from the first. If the document should mirror a page your users already see, rendering it in a current browser is usually less total effort than maintaining a downgraded stylesheet forever. The library versus API comparison works through that trade in detail.

Reason four: the render fired before the CSS finished loading

Less common than the first three, but it does happen, especially with large stylesheets or a slow asset host. If the renderer captures the page as soon as the HTML parses, an external stylesheet request may still be in flight. You get partial styling: some rules applied, others not, which is the most confusing symptom of all because it looks random and changes between runs.

Intermittency is the giveaway. If the same input produces a correctly styled PDF sometimes and a broken one other times, it is a race, not a support problem. Wait for the full load event rather than just the DOM, and if the page also builds content with JavaScript, wait for a marker element that only exists once rendering is complete. The guide to waiting for content before rendering covers the reliable signals, and inlining your CSS removes this failure entirely since there is nothing to fetch.

Reason five: modern color and layout syntax

A newer version of this problem started appearing with Tailwind CSS v4, which uses the oklch() color function by default. Any engine that cannot parse oklch() drops those declarations, so a page that is fully styled in Chrome renders with no colors at all in an older engine. The layout is intact, the type is right, and everything is black on white.

The same applies to CSS nesting, container queries, color-mix() and custom properties in engines with partial variable support. If your PDF looks structurally correct but colorless, check what color syntax your build emits before you go looking for anything more complicated. Either configure your build to output legacy color formats for the document stylesheet, or render in an engine that keeps pace with the browser your designers use.

A five-minute diagnostic order

When a PDF comes back wrong, run these checks in sequence rather than changing things at random:

SymptomMost likely causeFirst thing to try
Times New Roman, blue underlined links, one columnStylesheet never loadedInline the CSS or use absolute URLs
Styled, but wrong: nav hidden, backgrounds goneA print stylesheet is applyingEmulate print media in DevTools
Colors and fonts fine, layout stacked verticallyFlexbox or grid unsupportedCheck the engine's CSS coverage
Correct sometimes, broken other timesRender fired before CSS loadedWait for full load, or inline the CSS
Everything black and white, layout correctModern color functions unparsedCheck for oklch and similar syntax
Backgrounds and colors missing everywhereBackground printing is off by defaultEnable the print background option

That last row deserves its own note, because it looks like CSS loss and is not. Browsers deliberately skip background colors and images when printing, to save ink. If your styled boxes render as outlines with white interiors, the CSS is applying fine and you simply need to turn background printing on. That specific case is covered in the background not printing guide.

Two neighboring symptoms are worth ruling out separately, because they follow the same three causes as CSS but affect a single kind of asset. If your layout is right but the typeface fell back to Times or Arial, that is the web fonts not loading in HTML to PDF case. If the styling holds and the pictures come out as blank rectangles, especially the ones below the fold, see why images do not load in an HTML to PDF render, where lazy loading is the usual culprit.

The structural fix

Every cause above is really one question: is the thing rendering your document the same kind of engine as the browser your CSS was written for? When it is, this whole category of problem disappears, because there is no support gap to work around and nothing to downgrade. That is also why the fix so often outlasts the bug. Teams that move their document rendering to a current browser stop writing table layouts for the PDF version, and the design stops drifting away from the page it is supposed to mirror. If the markup you are rendering came from a template you did not hand-write, say a site whose pages were generated for you, that gap can be wider than you expect, since generated markup tends to lean on current CSS features.

If you would rather not run and scale that browser yourself, the PDF generator API does the render in managed Chromium and returns a searchable PDF with your CSS intact, and you can try it on any URL or HTML string in the converter at the top of this page. For a stack-by-stack view of which engines support what, the wkhtmltopdf alternative page maps the current options by language.

Written by the team building Sitepdf, an HTML to PDF API that archives every page it renders. The in-browser converter is free to try; early access locks the launch pricing.

§ Early access

Get on the early-access list

The API opens to the list first, in order. Early access locks the planned launch rates for 12 months. No card required, launching soon.

Render + archive, one API