Skip to content
Press.js Press.js Press.js Docs

Caveats

Press.js renders in Chromium and captures the visual output, but some web features that work well in interactive browsers cause problems in a paginated PDF context.

Press.js renders web content to PDF via Chromium. To understand why some effects degrade, it helps to know how PDF works under the hood.

PDF shares its graphics model with PostScript, a page description language developed by Adobe in the 1980s. In this model, a page is described as a sequence of drawing operations: fill a path, draw text at a position, place an image. Each operation is explicit — there is no concept of layout, style inheritance, or dynamic behavior.

CSS, by contrast, is a high-level declarative language. The browser computes layout, resolves cascading styles, and then must translate the result into PDF’s much simpler primitive operations. Effects that don’t map cleanly to paths, text, or images get rasterized into a bitmap — which loses sharpness and increases file size.

This fundamental gap explains the limitations below.

CSS gradients (linear, radial, conic) are rendered as raster images by Chromium’s PDF engine, producing visible banding and large file sizes. Use solid colors instead.

/* Avoid */
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
/* Prefer */
background: #667eea;

Shadows add visual depth on screen but appear as soft raster blobs in PDF output. They increase file size and can look muddy when printed.

/* Avoid */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
/* Prefer */
border: 1px solid #ccc;

Like box shadows, text-shadow is rasterized during PDF export, producing soft, low-resolution blobs around text that look visibly inferior to the screen rendering.

/* Avoid */
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
/* Prefer */
color: #333;

filter: blur(), drop-shadow(), contrast(), brightness(), and similar CSS filter functions rely on Skia image filters which the PDF backend cannot represent as vectors. Affected elements are fully rasterized, often at screen resolution (~72 DPI), producing pixelated output. They also add significant rendering cost.

backdrop-filter effects are expensive to render and often don’t survive the PDF pipeline correctly. Elements that rely on transparency against a blurred background may appear with a solid or incorrect background.

/* Avoid */
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(10px);
/* Prefer */
background: #f5f5f5;

mix-blend-mode and background-blend-mode (multiply, screen, overlay, difference, etc.) cannot be natively represented in PDF. Chromium rasterizes the affected region at screen resolution, resulting in pixelated output that does not hold up in print.

/* Avoid */
.overlay {
mix-blend-mode: multiply;
}
/* Prefer — pre-composite in a design tool, export as PNG */

While simple opacity values generally work, complex layered transparency — deeply nested translucent elements, overlapping transparent boxes, or combinations of opacity and blend modes — can force rasterization as the PDF engine struggles to preserve the composited result in vector form.

CSS clip-path: polygon(), clip-path: path(), and mask-image depend on compositor-layer rendering in the browser. Chromium’s PDF pipeline often strips or simplifies these effects, producing incorrect output or blank areas. For reliable clipping, use inline SVG <clipPath> elements referenced by clip-path: url(#...) — these have better PDF support.

/* Avoid */
.card {
clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}
/* Prefer — SVG-based clipping */

2D transforms (transform: rotate(), scale(), skew()) may force the affected element to be rasterized, depending on complexity. 3D transforms (rotateX(), rotateY(), translateZ(), perspective()) and will-change: transform trigger GPU compositing layers that are flattened to bitmaps during PDF capture — often at low resolution.

/* Avoid — triggers rasterization */
.promo {
transform: perspective(1000px) rotateY(5deg);
}
/* Prefer */
.promo {
/* static layout, no 3D transform */
}

border-image with sliced or repeated images often fails to map cleanly to PDF primitives. The border may appear as a solid block, be completely missing, or force rasterization of the element.

/* Avoid */
border-image: url(border.png) 30 stretch;
/* Prefer */
border: 2px solid #333;

-webkit-box-reflect is a non-standard property that is not supported in the PDF pipeline. Reflections are either omitted or cause the entire element to be rasterized.

Continuously changing elements (animations, breathing lights)

Section titled “Continuously changing elements (animations, breathing lights)”

CSS animations (@keyframes), transitions, and JavaScript-driven visual effects that continuously change element appearance (such as pulsing/breathing lights, blinking indicators, marquees, or auto-scrolling text) rely on an interactive runtime. In a static PDF these effects freeze at an arbitrary frame or are not captured at all, leading to broken or misleading visual output. Keep your document design static and avoid time-dependent visual effects.

/* Avoid */
.notification-dot {
animation: pulse 2s ease-in-out infinite;
}
/* Prefer */
.notification-dot {
background: #e53e3e;
}

position: fixed works in the browser viewport but does not map cleanly to paginated output. Use Press.js <Header> and <Footer> components for repeating content instead.

Content that overflows its container (overflow: hidden, scroll, auto) is clipped in the rendered page. Press.js will warn about overflow during rendering, but the content may be lost in the PDF. Use overflow: visible or adjust your layout to avoid clipping.

Both Flexbox and CSS Grid are supported by Chromium’s PDF renderer, but they can produce unexpected page breaks. A flex or grid container whose children span multiple pages may split in awkward places — one child renders on one page while its sibling wraps to the next. Use <KeepTogether> around logically indivisible groups and avoid relying on flex/grid layout to control page-level breaks.

Absolutely and relatively positioned elements, particularly those relying on complex z-index stacking contexts, may not position correctly after pagination. The page break can separate a positioned element from its containing block, producing misplaced or overlapping content.

Multi-column layout (column-count, column-width) is not fully supported in the pagination pipeline. Content in multi-column containers may not paginate correctly.

CSS properties widows, orphans, page-break-inside, page-break-before, and page-break-after have inconsistent support across Chromium versions in the PDF rendering path. Do not rely on them for critical layout control — use Press.js components (<KeepTogether>, <KeepNext>, <PageBreak>) instead.

shape-outside creates complex float shapes that are not rendered in PDF. Affected text will flow as if no shape is applied.

CSS regions (flow-into, flow-from) and exclusions (wrap-flow) are experimental features that are not supported in Chromium’s PDF output. Content using these features will be collapsed or missing.

Tables are split row-by-row across pages, which works well for most data. However, tables with deeply nested sub-tables or cells containing large blocks of text may not split as expected. Use <KeepTogether> sparingly around table cells.

Native HTML form controls (<input>, <select>, <textarea>, <button>) are rendered using the operating system’s native widgets during screen display. In PDF output, these are captured as static visual snapshots — they look like controls but are not functional. Custom-styled controls generally render better than native ones.

Styles applied via :hover, :focus, :active, and :focus-within pseudo-classes are interactive-only. In a static PDF, only the default (un-hovered, unfocused) state is captured if content is rendered with the browser’s print media path. Press.js captures the visual output after page load, so some JavaScript-activated states may be preserved, but you should not rely on hover-dependent content.

Embedded iframes are captured as atomic units. Their content may not load before the PDF is generated (especially cross-origin iframes) or may simply appear blank. If you must include embedded content, ensure it loads synchronously and is the same origin.

<video> and <audio> elements cannot play in a PDF. The video element displays its poster image (if set) or a blank area. Audio elements are invisible. Neither is useful in a document format.

The <details> element renders in its closed state in the PDF, hiding its content. There is no mechanism to “open” it for capture. Use the open attribute to ensure content is visible:

<details open>
<summary>Section title</summary>
<p>This content will be visible in the PDF.</p>
</details>

The <dialog> element and the Popover API require JavaScript to open. If the dialog is not open when the page is captured, it will be invisible. Ensure dialogs and popovers are in their visible state at capture time.

Images and iframes using loading="lazy" may not have loaded by the time the page is captured. Use loading="eager" or ensure content is fully loaded before triggering the render. The same applies to content-visibility: auto — content off-screen during capture may be omitted.

<!-- Avoid — may not load before PDF capture -->
<img src="chart.png" loading="lazy" alt="" />
<!-- Prefer -->
<img src="chart.png" loading="eager" alt="" />

contenteditable elements are captured as plain content — editing capabilities are not relevant in a static PDF. Any rich text formatting applied through JavaScript editing libraries (TipTap, Quill, etc.) should be rendered to static HTML before capture rather than relying on in-place editing state.

SVG filter effects (<feGaussianBlur>, <feDropShadow>, etc.) are rasterized by the PDF engine, just like CSS filters. Additionally, <foreignObject> inside SVG (which embeds HTML inside SVG) has limited support in Chromium’s PDF pipeline and may not render at all.

<canvas> elements using WebGL or WebGL 2.0 are captured as raster bitmaps — they are not blank, but the output is limited to screen resolution and cannot be split across pages. The result often looks visibly softer than the interactive rendering. If you need charts, diagrams, or visualizations, prefer SVG whenever possible — SVG renders as vector primitives in the PDF, stays sharp at any zoom level, and can be split across pages.

CSS colors are specified in the sRGB color space. PDF supports CMYK and other color spaces, but Chromium does not perform color space conversion for PDF output. Colors that cannot be represented in CMYK will be clipped. If color-accurate print output is required, design within sRGB gamut or use a separate color management tool.

When Chromium generates a PDF, embedded images may be re-encoded depending on format. WebP and AVIF images may not be directly embeddable and could be transcoded, potentially losing quality. PNG and JPEG images have the most reliable PDF embedding path.

Use PNG or JPEG for images that must remain sharp in PDF
WebP and AVIF may be transcoded during PDF generation

Variable fonts (OpenType Font Variations) may not embed correctly in the PDF. The PDF specification does not natively support font variations, so the browser must expand them to individual static instances. This can increase file size and, in some cases, produce incorrect glyph rendering. When possible, use static font weights instead of variable axis values.

hyphens: auto has known issues in Chromium’s headless (PDF-generation) rendering path on Linux and Windows. Automatic hyphenation may not apply, producing overlong lines or excessive word spacing. Use explicit soft hyphens (&shy;) where hyphenation is critical.

The CSS element() function (which displays a clone of another element as a background image) is not supported in Chromium’s PDF rendering path. Elements using it will appear blank.

Every element in your document is measured and tracked. Very large documents (10,000+ DOM nodes) may show slower render times. Consider simplifying complex layouts or reducing deeply nested structures.

SVG elements are individually measured, and deeply nested SVG structures can significantly slow down rendering. Flatten SVG hierarchies and remove unnecessary grouping (<g>) elements.

Canvas elements are captured as atomic units (they cannot be split across pages). Ensure canvas-based visualizations fit within a single page. For larger charts, consider rendering them as SVG, which the engine handles more gracefully.

All fonts in your document must be explicitly declared via @font-face with hosted font files. System fonts are not portable across machines (your dev machine, CI server, and Cloud renderer all have different font libraries). See the Fonts page for details.

Full-page background images are embedded at their natural resolution regardless of how they are scaled via CSS background-size. A 5000×3000px background image displayed as cover still embeds the full 5000×3000px image. Use appropriately sized images for backgrounds and prefer <img> elements when you need precise display-size embedding.

Images are included in the PDF at their natural resolution. Scale images to their display size rather than relying on CSS width/height to downscale. A 4000×3000 image displayed as 400×300 still embeds the full resolution in the PDF.

Documents that use many CSS counters (such as automatic chapter numbering, figure numbering, and list counters across hundreds of pages) add measurement overhead. Each counter increment requires the layout engine to track state. Use counter-based numbering for reasonable document sizes (under 200 pages) and consider manual numbering for extremely large documents.

Each @font-face declaration with a unique font file adds to the PDF’s embedded font set. Using more than 4–5 font weights dramatically increases file size. See the Fonts page for recommendations on keeping font data efficient.